JavaScript Browser Event loop 如何運行?
最後更新:·發布日期:

前言
JavaScript 是單執行緒語言,另一種說法叫做單線程,核心運行是同步的方式完成,而 JavaScript 可以靠著非同步的方式完成操作,則是因為 JavaScript 執行環境的關係,可能是 Browser 或 Node 環境,它們兩者分別提供 Web APIs 及 Node APIs,搭配 Event loop 的概念,讓 JavaScript 同步方式不會造成阻塞問題,而 Event loop 如何運行呢?
Browser 跟 Node 的 Event loop 有些微差異,此篇文章講解 Browser
Event loop 經過區域
- Heap(堆):物件與閉包等配置的記憶體區域。
- Stack(堆疊):當有函式呼叫都會進入 Stack,特性是後進先出(LIFO)。在其它文章可能會跟 Heap 的堆搞混,但它們兩者是不相同的東西。
- Task queues(任務佇列,俗稱 Macrotask; 宏任務):存放宏任務的 Callback function,每輪只會執行一次,而屬於宏任務的有整體
script
腳本、setTimeout
、setInterval
、I/O等,而 queue 的執行方式為先進先出(FIFO)。 - Microtask queue(微任務佇列):存放微任務的 Callback function,會優先於宏任務的 Callback function,每輪會完全清空,屬於微任務的有
Promise.then
、Promise.catch
、Promise.finally
、queueMicrotask
、async await
的 await 後續邏輯等,這也保持相同 Queue 的基本概念先進先出(FIFO)。 - Event loop(事件循環):不斷監聽 stack 是否為空,如為空後執行 Microtask 清空,再執行 Macrotask 一次,循環執行直到整體 Event loop 完成。
Event Loop 核心規則
- 執行同步代碼(整體 script 腳本),如碰到 I/O 或 setTimeout 這些 Web APIs 的呼叫,會丟進 Web APIs 執行
- 同步代碼執行完成後,檢查 Call Stack 是否為空
- 如果 Call Stack 為空,完全清空 Microtask queue
- 執行一個 Macrotask(從 Task queue 取出)
- Event Loop,重複步驟 2-4,直到所有任務完成
Event loop 運行方式
可以先使用第一直覺判斷下列程式碼的運行方式認為是怎麼樣?
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
你可能會認為列印出的順序是 1 → 2 → 3 → 4,但實際上列印出的順序是 1 → 4 → 3 → 2。主要就是整體 Event loop 的循環的方式問題,像在上面 Event loop 區域有講解到的,Promise.then
是 Microtask,setTimeout
是 Macrotask,而 Microtask 會優先 Macrotask 的執行,直到 Microtask 為空後才會執行 Macrotask。
圖示說明
程式碼註解說明
// 同步程式碼
console.log(1);
// 先到 Web APIs 執行後,註冊到 Macrotask
setTimeout(() => {
console.log(2);
}, 0);
// 註冊到 Microtask
Promise.resolve().then(() => {
console.log(3);
});
// 同步程式碼
console.log(4);