快速入門 Chrome 底層:事件循環(Event Loop)

關於 Chrome

Chrome 是 multi-process, multi-thread 應用程式

可以簡單當作一個 process 就是一個應用程式(比如LINE、VS Code…),而一個 process 裡可以有多個 thread (即多工)

現在每開啟一個新分頁都是一個 process(相當於開啟一個新程式),優點是一個分頁崩潰,不會影響其他分頁。代價就是讓 Chrome 變成吃記憶體怪獸。所以網路上常看到各種抱怨Chrome 肥大,就是這個原因

比如你開了 3 個 Facebook 分頁,相當於開了 3 個應用程式

未來 Chrome 有打算改成同個網站共用同個 process: 即一個網站就是一個應用程式,以降低吃太多資源的問題


Chrome 作為一個多工的應用程式,承載了許多工作。比如網路交互、套件管理、網頁渲染…每一項都是一個 process

其中前端最關注的網頁渲染,就是一個 process
這 process 裡面有許多 thread。如 render main thread, network thread…等

其中render main thread就是本篇的重頭戲: 事件循環
他是一個 single thread, non-blocking, asynchronous: 單執行緒、不阻塞、非同步(異步)

Render Main Thread

單線程是異步產生的原因
事件循環是異步的實現方式

單線程,代表一次只能做一件事。比如發一個 request、處理一次按鈕點擊

程式執行是非常單純的,一定由上往下依序執行
若其中有程式執行太久,就會影響其他任務,也就是阻塞
比如在發 request 的時候,user點擊了按鈕
勢必得等待 request 回應後,才能繼續處理 user 的按鈕點擊
顯而易見就會有明顯的網頁卡頓感

所以需要異步來解決這卡頓感,也就是第一句所說的: 單線程是異步產生的原因

網頁的互動是非常多且複雜的
每多產生一個事件,比如點擊按鈕、ajax請求、上下滾動的頁面重繪…等,都是一個事件

既然有這麼多事件要處理,那就排隊處理,一個一個來唄!

所以每產生一個事件,就會送進隊列,Render Main Thread 就會依照進來的順序逐一執行所有事件
這個隊列,就是事件循環(event loop)

也就是第二句: 事件循環是異步的實現方式

上述說明簡化了許多事情。實際上,Chrome 不會只有一個隊列,他有很多的隊列在處理各式各樣的事情

事件循環(Event Loop)

依據 W3C 規定,browser 實作商一定要有微隊列,他是最高優先級。
這裡面只要有增加事件,就是第一優先執行
也就是所謂的 VIP 通道

至於其他部份,則依 browser 自行實作決定

在 Chrome 中,跟前端比較有關的主要有下列 3 個隊列,執行的優先順序亦如下

  1. 微隊列:W3C規定,最高優先級(VIP)。放在這裡的函式優先執行。比如Promise
  2. 交互隊列:Chrome 認為使用者的互動是非常重要的,所以執行順序在第二
  3. 延時隊列:就是setTimeout(), setInterval()這種計時函式。時間到了才會執行,排第三順位

在其他教學文章通常簡單區分為宏隊列(macro)、微隊列(micro)。單純這樣區分其實不好理解

比如以下例子

setTimeout(() => console.log(3), 0); // 將`函式3`放到【延時隊列】
Promise.resolve(() => console.log(2)); // 將`函式2`放到【微隊列】
conosle.log(1); // 直接執行

最終輸出結果為

1
2
3

原因如下:
程式一定由上往下執行

  1. 先執行setTimeout,設定 0 秒後執行函式3,時間馬上到,將函式3放到【延時隊列】
  2. 執行 Promise 函式,由於權限最高,將函式2放到【微隊列】
  3. 執行 conosle.log(1),本段 JS 結束 ==> 輸出 1
  4. Render Main Thread 手上沒事,接著去隊列找下一件事情,先檢查【微隊列】,有工作。領出來執行他
  5. 執行 Promise 裡的函式2conosle.log(2) ==> 輸出 2
  6. Render Main Thread 手上沒事,接著去隊列找下一件事情,【微隊列】沒東西,再去【交互隊列】,仍沒東西,最後到【延時隊列】,取出函式3
  7. 執行setTimeout裡的函式3conosle.log(3) ==> 輸出 3

可以看到,setTimeout 裡的函式3是最早進入隊列的,卻是最後一個執行

JS 裡的計時器是一定準確的嗎?

W3C規定 setTimeout, setInterval 等計時器
在嵌套到第 5 層後,一定會有 4ms 的誤差;第 6 層再誤差 4ms,依此疊加

但實際執行誤差會更大,因為受事件循環影響,他有可能被插隊
所以真實執行時間會更後面