最近10餘篇文章的一篇彙總

前言

這個月基本都是記錄一下面試時的一些知識點,這裏找自己記得不是很熟練的彙總一下。

手寫

手寫部分,掌握的不熟的主要有深拷貝,JSONP和event bus。

function dcopy(obj){
  let map = new WeakMap()
  let help = function(obj){
    if(typeof obj!=='object' && typeof obj!=='function') return obj
    if(map.get(obj)) return obj
    map.set(obj, true)
    let proto = Object.getPrototypeOf(obj)
    let tar = new proto.constructor()
    if(Object.prototype.toString.call(tar)=='[object Set]') {
      obj.forEach(v=>{
        tar.add(dcopy(v))
      })
    }
    for(let i in obj){
      if(obj.hasOwnProperty(i)){
        tar[i] = help(obj[i])
      }
    }
    return tar 
  }
  return help(obj)
}
function jsonp({url,params,callback}){
  return new Promise((resolve, reject) => {
    let script = document.body.createElement('script')
    params = JSON.parse(JSON.stringify(params))
    let arr = []
    for(let key in params){
      arr.push(`${key}=${params[key]}`)
    }
    arr.push(`callback=${callback}`)
    script.src = `${url}?${arr.join("&")}`;
    document.appendChild(script)
    window[callback]=function(data){
      if(!data) reject('failed')
      resolve(data) 
      document.body.removeChild(script) 
    }
  })
}
class Event {
  constructor() {
    this.handlers = {};
  }
  on(event, cb) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event].push(cb);
  }
  emit(event, ...args) {
    if (this.handlers[event]) {
      this.handlers[event].forEach(callback => {
        callback(...args);
      });
    }
  }
  off(event, cb) {
    const callbacks = this.handlers[event];
    const index = callbacks.indexOf(cb);
    if (index !== -1) {
      callbacks.splice(index, 1);
    }
  }
  once(event, cb) {
    const wrapper = (...args) => {
      cb.apply(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

Vue響應式

首先利用Proxy或Object.defineProperty生成的Observer既充當了監聽器的作用,又充當發佈者的角色,在數據屬性變化後來通知訂閱者;同時Compile解析模板中的指令,收集指令依賴的方法和數據,等待數據變化後的渲染;最後watcher連接兩者,使得訂閱依賴的數據變化轉化爲視圖的變化。

同時Proxy優化了Object.defineProperty對於新增屬性不被攔截、不需要遍歷對象屬性進行修改等問題

IFC

IFC的行框高度由它包含元素的最高高度來決定。在一個行內格式化上下文中,盒是一個接一個水平放置的,從包含塊的頂部開始,同時這些盒之間的水平margin,border和padding都有效。

  1. 創建一個IFC,用其中一個元素撐開父元素的高度,然後設置其vertical-align:middle,其他行內元素則可以在此父元素下垂直居中。
  2. 當inline-level box寬度大於父容器寬度時會被拆分成多個inline-level box,左邊距對第一個inline-level box生效,右邊距對最後一個inline-level box生效。

V8 Hidden Class

首先,JS作爲動態類型的語言,對象屬性是可以在運行過程中動態的增刪改的,若使用類似字典的數據結構來存儲對象的屬性,相對於靜態類型的語言,動態查找屬性在內存中的位置要慢的很多,爲了提高訪問速度,V8使用動態的創建hidden_class的形式去優化。

一個直白的例子:

function fn(){this.a = a;this.b = b}

當我們new fn() 時,首先會創建一個空的hidden_class,稱爲c0,接着執行到第一條語句時,會在c0的基礎上創建c1,c1代替c0成爲這個對象新的hidden_class,以此類推,最後c2是這個對象的hidden_class。

動態創建hidden_class的行爲看起來很低效,但是當hidden_class可以被反覆利用的時候,這種方式看起來就高效多了。

通知這種方式也是有侷限性的,如如果我們使用delete方法,對象又會倒退回字典查找模式,刪除屬性,並進一步優化,代價很大。

node多進程

進程與線程的區別,前者是系統調度運行的基本單位,後者是CPU調度運行的基本單位,前者是後者的容器。Node開啓多進程不是爲了解決高併發,主要是解決了單進程模式下 Node.js CPU 利用率不足的情況,充分利用多核 CPU 的性能。

Nodejs創建多進程的方式主要有cluster和child_process兩種方式。兩者都是以fork一系列的API爲主開闢新的進程,後者更加靈活,前者通過需要通過一個主進程管理多個工作進程,通過循環算法實現負載均衡,能夠更簡單的提高多核的利用率。

創建多進程並不是終點,能夠實現進程間的通信,即IPC纔是真正能夠提升效率的表現,Node中實現IPC通道是依賴於libuv的,父進程在實際創建子進程之前,會創建IPC通道並監聽它,然後才真正的創建出子進程。

幾個React常問的問題

  1. Fiber:React利用了requestIdleCallback,同時對不支持這個API的瀏覽器添加了polyfill。在這個空閒時間內,從根節點開始遍歷 Fiber Node,並在一段時間後交還給瀏覽器。React也爲了Fiber架構做出了大量的調整,由於之前同步的、遞歸的Stack Reconcilation無法隨意中斷,也很難被恢復,不利於異步處理等特點,React轉而使用了鏈表來模擬棧結構。最終變現爲兩個階段,一個是協調,一個是提交,前者可以中斷,後者仍然需要一口氣執行到底。
  2. React爲什麼需要合成事件:更好的兼容性,擺脫DOM事件,掛載到document上,統一管理事務。
  3. React爲什麼需要默認支持SCU,它原本可以實現一個深比較,但是開發者如果不去遵循不可變數據的規則,SCU默認是不會執行的,數據變化了也不會渲染,因爲a.push後,a==a。所以通過手動優化雖然繁瑣,但是確實減少了React的BUG代碼。同時深度比較也是消耗性能。
  4. React.nativeEvent.currentTarget可以拿到原生事件綁定的位置,指向的是document,即事件都掛載到document上。
  5. 從子->父的角度看Redux:創建一個最頂層的state,把數據當作props向下傳。這個最頂層的存放state的地方即爲store;傳遞了函數給子組件, 希望子組件通過傳遞參數’給父組件來改變父組件的狀態。Redux也類似,我們可以把action看成傳遞的參數,因爲action也不過是個js對象而已;而reducer則可以看出回調函數,他會根據action的信息來做出處理,正如Redux種reducer接受了dispatch的action後返回新的state一樣。

簡單認識websocket

WebSocket建立在 TCP 協議之上,與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段採用 HTTP協議,同時它的數據格式比較輕量,性能開銷小,通信高效。可以發送文本,也可以發送二進制數據。它不受沒有同源限制,客戶端可以與任意服務器通信。協議標識符是ws(如果加密,則爲wss),服務器網址就是 URL。

HTTP握手後,WebSocket基於HTTP協議進行升級,服務端返回101狀態碼標識協議切換,後序的數據交互都按照新的協議進行。建立連接後,後續的操作都是基於數據幀的傳遞。

WebAssembly

JIT的出現確實帶來了可觀的性能提升,但是受限於JS動態類型和運行時編譯的特點,判斷類型成爲了限制JIT的一個重要因素。對此Mozilla提出了asm.js,通過註解和檢測等手段確保強類型,減少JS依據上下文判斷類型的損耗,優化機器碼的解釋過程。

後來各家各戶覺得Mozilla這思路挺好,於是聯合起來,設定了WebAssembly,即一份字節碼標準,以字節碼的形式依賴虛擬機在瀏覽器中運行。

WASM並不能達到Assembly的性能,它只是一種約定的字節碼,既然是字節碼,肯定還是需要解釋器翻譯成機器碼執行。它的優點是相對於JS字節碼,它的翻譯速度要快上許多。

其次對於CPU密集型任務,WASM也只是多提供了一種選擇,有些時候使用WEBGL來做GPU加速往往是更好的選擇。

最後,並不是指使用WASM就一定比JS快,V8使用的JIT有個很重要的優點,就是它會對熱點代碼進行抽取,直接編譯生成二進制機器碼方便接下來的調用。

路由

URL改變的情況下不刷新界面,如何在不刷新界面的狀態下去檢測到URL的改變。

從Hash路由來說,因爲錨點的存在,當我們去修改錨點後面的數據,頁面本身並不會發生改變,因此也就不存在上面的第一個問題,接着,我們通過hashChange這個事件來監聽路由的變化,當我們使用瀏覽器的前進後退;當我們點擊a標籤;當我們使用window.location去改變URL時,這個事件都會被觸發。因此第二個問題也比較方便的解決。

從History路由來說,H5之前,我們熟悉的是go,back等方法,H5之後,爲了實現前端路由,History又引入了新的API,如pushState,replaceState兩個方法,這兩個方法對路由的改變並不會引起頁面的刷新,因此解決了第一個問題。同時類似hashChange事件,我們還實現了popState來監聽瀏覽器前進後退;同時我們又通過攔截pushState和a標籤的點擊來解決第二個問題。

二維碼大概原理

用戶進入一個網頁後,服務器生成一個uid來標識用戶,而顯現出的二維碼則標識了對應uid的鏈接。當使用正確的客戶端打開這個鏈接時,服務器會收到用戶的相關信息,並在客戶端點擊確認登陸後生成一個token給對應網頁。之後的交互都依賴這個token。

Webpack生命週期

  1. 初始化參數,從配置文件或shell中
  2. 初始化Compile,加載插件,執行run方法開始編譯
  3. 根據entry找到入口文件
  4. 從入口文件出發,執行所有的loader對模塊編譯,找到該模塊依賴的模塊,再遞歸的執行,知道所有編譯結束。
  5. 拿到模塊翻譯後的最終內容和依賴關係
  6. 根據入口和模塊的依賴關係,打包成一個個chunk,把每個chunk轉換成輸出文件加入輸出列表
  7. 根據輸出配置輸出路徑和文件名

Webpack的XHR

  1. 在watch模式下,webpack監聽到文件的變化,重新打包並保存到內存中
  2. 通過sockjs在瀏覽器和服務端建立一個websocket長連接,將 webpack 編譯打包的各個階段的狀態信息告知瀏覽器端,最主要的是新模塊的hash值。
  3. 客戶端某模塊接收到新模塊的hasn值,發送AJAX請求,服務端返回一個json,上面有所有需要更新的模塊hash,根據這個更新列表發送JSONP。
  4. 檢查新舊模塊更新與否,更新模塊依賴等。

移動端基本知識

device pixels,我們可以理解爲一個點,有紅藍綠三個彩燈通過不同的亮度顯示最後的色彩。

接着是設備獨立像素,區別於上面的知識,設備獨立像素可能包含多個設備像素。而1個CSS像素 等於 1個設備獨立像素,設原來分辨率爲100100,元素爲5050,當我們分辨率改成200*200,元素自然顯得更小了。

2K和4K就不說了,說說PPI,5.8英寸、分辨率爲1125x2436的iphonex,ppi = Math.sqrt(11251125 + 24362436) / 5.8=463ppi。DPR則是物理像素除以設備獨立像素。

掌握了上面的基本知識,再看viewport就很好理解了,假如你使用!+Tab,html文件會自動生成:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

這意味着當我們使用手機端測試時,寬度會默認爲設備的寬度,如iphone設爲375等。這樣字體就不至於縮放的很小,否則,大部分瀏覽器會以980的寬度進行渲染。

initial-scale定義了初始縮放比例,maximum-scale定義了用戶最多能放大多少倍,minimum-scale定義了最大能縮小多少倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章