跳至主要内容

JavaScript - Event Loop

開始之前

  • JavaScript 引擎是 單執行緒 ( Single Thread ),也就是 一次只做一件事 / 一次只執行一段程式碼
  • 瀏覽器不只有 JS 引擎,也就是整個瀏覽器並不是只由 JS 引擎組成,而一個分頁 ( Tab ) 僅存在一個 JS 引擎運行 JS 程式。
  • 瀏覽器是屬於多程式多執行緒,因此可以在瀏覽器中做出非同步行為。
  • Event Loop 顧名思義,就是一個 Loop ( 循環 ),會持續檢查執行堆疊 ( Call Stack ) 有沒有 Callback 可以呼叫。
瀏覽器核心組成示意圖瀏覽器核心組成示意圖

JavaScript Runtime ( JavaScript 執行環境 )

V8 Engine

Runtime

Stack 是一個先進後出 ( First In Last Out ) 的運作模式,當程式呼叫函式 ( Call function ) 時會把函式放進 Stack 中,直到函式執行結束 return 後再將函式從 Stack 中取出 ( 清空 )。

範例 :

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {
console.log('foo')
bar()
baz()
}

foo()

以上的程式碼執行後在 Call Stack 是這樣運作的 :

(來源 : https://flaviocopes.com/node-event-loop/)

以瀏覽器來說,之所以能夠同時做事的原因是,瀏覽器不只有 Runtime 而已。

Web APIs

Web API 範例 :

console.log('Sync 1')

setTimeout(function asyncTimer() {
console.log(`Hey, I'm done.`)
}, 0)

console.log('Sync 2')
執行結果
Sync 1
Sync 2
Hey, I'm done.

Blocking 阻塞

執行堆疊 ( Call Stack ) 清空時,瀏覽器會做兩件事情 :

  1. Macrotask Queue ( 宏任務佇列 )Microtask Queue ( 微任務佇列 ) 有任務存在,會讀取其任務 ( 也就是 Event Loop ) 在 Call Stack 上執行完畢並清除。
  2. Render ( 渲染畫面 ),瀏覽器理想上會進行最快速的重繪頻率 ( 顯示器更新頻率 ) 。
信息

當 Call Stack 還沒有清空之前,瀏覽器是不會重新渲染畫面, 因此若 Call Stack 上的 Callback 函式執行過久,就會產生所謂的 阻塞

瀏覽器上模擬阻塞 :

const delay = 5000
const end = Date.now() + delay
console.log('blocking start')
while (Date.now() < end) {}
console.log('blocking end')

Event Loop

宏任務 Macrotask

包含的方法 :

  1. 前面提到的 Web APIs
  2. I/O ( 讀寫、存取 )
  3. 載入 JS 檔案並且執行時,例如 <script>
  4. 渲染畫面 ( Render )

微任務 Microtask

包含的方法 :

  • Promise
  • MutationObserver ( 監聽 DOM tree 變動的 API )
  • Process.nextTick ( 屬於 Node.js 的 Event Loop )
信息

注意這邊的微任務 ( Microtask ),指的是 已經回應後的結果,而且正等待執行中。 例如 : Promise 進行的 Ajax 請求已經確認執行完畢 ( Settled ),此時 .then( ... ) 或 .catch( ... ) 將會進入微任務佇列 ( Microtask Queue ),等待執行。

每當 宏任務 執行完畢時,會檢查有無 微任務 ,若有則執行微任務,當所有微任務執行完畢,就會進行一次畫面渲染 ( Render Task ),渲染 ( Render ) 的動作本身也是一個 宏任務

範例 :

setTimeout(() => console.log('Timer'))

Promise.resolve().then(() => console.log('Promise'))

console.log('Sync Console')
執行結果
Sync Console
Promise
Timer

提示

當頁面載入,或者在 devtools console 執行,都相當於執行 <script>,而執行<script>就等於執行一個 宏任務 ( Macrotask )


當微任務執行完畢後,才會渲染畫面

整理上述,渲染畫面有幾個時機 :

  1. 當 Call Stack 的 Callback 執行完畢被清空時。
  2. 當宏任務 ( Macrotask ) 在 Call Stack 執行完畢被清空,且沒有微任務 ( Mircotask ) 時。
  3. 若有微任務 ( Microtask ),當微任務在 Call Stack 執行完畢時被清空時。

前端面試遇到面試官提問 Event Loop 是什麼該如何回答 ?

JavaScript 引擎是單執行緒,也就是同一時間、一次只做一件事,但瀏覽器中不只有 JavaScript 引擎,還有 Web APIs、GUI、Plugin 等處理程序提供我們非同步操作。

當非同步操作有執行的結果,會被放到 Event Queue,等待 JS 引擎中 Call Stack 同步函式執行完畢後,被放進 Call Stack 接續執行。這種監控 Event Queue 並且將任務放進 Call Stack 執行的行為,就是瀏覽器協調事件的機制 Event Loop。


Reference