Skip to content

2025-12-04

/ 13 分鐘閱讀

/ Advanced Web Dev Quiz

瀏覽器的呼叫堆疊與事件循環 | Call Stack & Event Loop ​

此文章是 FrontendMaster 上的 Advanced Web Development Quiz 課程筆記

問題:排序以下數字在主控台上出現的順序 ​

javascript
setTimeout(() => console.log(1));
Promise.resolve().then(() => console.log(2));
Promise.resolve().then(() => setTimeout(() => console.log(3)));
new Promise(() => console.log(4));
setTimeout(() => console.log(5));

Call Stack 呼叫堆疊 ​

呼叫堆疊是 JavaScript 引擎用來管理和追蹤函式執行順序的資料結構,它遵循 LIFO (Last in First out) 的規則,就像疊盤子一樣,最後被疊上去的盤子會先被拿去執行。

當程式執行時,呼叫堆疊會按以下步驟運作:

  1. 推入(Push):函式被呼叫時,會建立一個執行環境框架(execution frame),推入堆疊頂部
  2. 執行:JavaScript 引擎執行位於堆疊最頂端的函式
  3. 彈出(Pop):函式執行完畢或遇到 return 時,從堆疊頂部移除
  4. 返回:程式控制權回到前一個函式繼續執行

範例 ​

javascript
function firstFunc() {
  secondFunc();
}
function secondFunc() {
  thirdFunc();
}
function thirdFunc() {
  console.log('執行完畢');
}
firstFunc();

堆疊變化過程:

  1. 全域執行環境(Global Execution Context)進入堆疊
  2. firstFunc() 被呼叫,推入堆疊頂部
  3. secondFunc() 被呼叫,推入堆疊頂部
  4. thirdFunc() 被呼叫,推入堆疊頂部
  5. thirdFunc() 執行完畢,彈出堆疊
  6. secondFunc() 執行完畢,彈出堆疊
  7. firstFunc() 執行完畢,彈出堆疊
  8. 全域環境清空,程式結束

Event Loop ​

雖然 JavaScript 是單執行緒,但是透過執行環境(瀏覽器或 Node.js) 提供的事件循環 (Event Loop) 機制,讓耗時的非同步事件 (例如向伺服器請求資料) 不會阻礙主線程的執行,讓 JavaScript 可以在適當時機執行非同步的任務。

事件迴圈的工作流程可以分為以下步驟:

  1. 執行同步任務:所有同步程式碼在呼叫堆疊(Call Stack)中依序執行
  2. 處理異步任務:遇到 setTimeout、fetch 等異步操作時,交由 Web API 處理,完成後將回調函式放入任務佇列
  3. 檢查堆疊:當呼叫堆疊清空後,事件迴圈開始運作
  4. 優先處理微任務:先檢查微任務佇列(Microtask Queue),將所有微任務依序推入堆疊執行
  5. 處理宏任務:微任務清空後,從宏任務佇列(Macrotask Queue)取出一個任務執行
  6. 持續循環:重複上述步驟,直到所有任務完成

任務類型差異 ​

  • 宏任務(Macrotask)

    • 包含:
      • script (整理程式碼)
      • setTimeout / setInterval callback、postMessage、MessageChannel
      • UI 渲染任務
      • 用戶輸入事件
      • 網路事件
    • 一次只執行一個任務
  • 微任務(Microtask)

    • 包含:
      • Promise.then() / .catch() / .finally()
      • queueMicrotask()
      • async/await
      • MutationObserver callback
    • 每次會執行到清空

這兩個任務都遵循 FIFO (First in First out),最早被放進去的會最先被取出來執行。

答案: ​

4 2 1 5 3

答案說明

  1. 執行 macro task (執行整個 script)
  2. 第一行,放進 call stack,setTimeout 的 callback (() => console.log(1);) 放進 macro task
  3. 第二行,放進 call stack 並執行 Promise.resolve(),接著把他的 callback (() => console.log(2);) 放進 micro task
  4. 第三行,放進 call stack 並執行 Promise.resolve(),接著把他的 callback (() => setTimeout(() => console.log(3));) 放進 micro task
  5. 第四行,new Promise(() => console.log(4)); 因為 new Promise 的參數是同步的,所以 () => console.log(4) 會直接放到 call stack 裡面並被立刻執行。印出 4
  6. 第五行,放進 call stack 並執行 setTimeout,把 () => console.log(5); 放進 macro task
  7. call stack 清空,所以開始處理 micro task queue,其中最先放進去的第二行 (第 3 步) 放到 call stack 並執行。印出 2
  8. 接續執行 micro task queue 中的下一個 (第 4 步),將 setTimeout callback (() => console.log(3)) 放進 macro task,此時裡面已經有其他的 macro task 所以會排在最後面
  9. macro task queue 中剩下的都是 setTimeout 的 callback,所以依序進行以下步驟,放進 call stack 清除該 macro task 執行下一個,依序印出 1、5、3

這個 網站 視覺化 Event Loop 的執行,可以嘗試看看

參考 ​

最後更新時間:

Buy Me A Coffee

系列章節 第 4 篇 / 共 27 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash