JavaScript 筆記:Execution Stack / Context 與 Event Loop
說到 JavaScript 的非同步,我剛開始學習時常常被搞得一頭霧水。明明 setTimeout 設定為 0 毫秒,為什麼還是會比 Promise 晚執行?為什麼有時候程式的執行順序跟想像中完全不一樣?
在深入學習 JavaScript 的過程中,我發現這些「奇怪」的現象背後,其實有一套很有邏輯的運作機制。也因此整理了這篇筆記,經過消化之後紀錄我在學習過程中對以下概念的理解:
- 執行環境與執行堆疊:搞懂 JavaScript 怎麼管理函式調用
- JavaScript Runtime:瞭解除了 JS 引擎還有哪些重要角色
- Event Loop 機制:理解同步與非同步任務協作的觀念
- Task Queue vs Micro Task Queue:釐清不同異步任務的執行優先級
執行環境 (Execution Context, EC) 與執行堆疊 (Execution Stack, ES)
在 JavaScript 執行時,每個任務(job)都會有自己的「執行環境」(Execution Context, EC)。JavaScript 會用一個「堆疊」(Execution Stack, ES)來管理這些執行環境。當有任務要執行時,它的執行環境就會被加到堆疊上。每個任務執行時,系統都會建立一個執行環境,裡面包含:
- 變數:
var、let、const、函式、arguments等。 - 範疇鏈 (Scope Chain):決定變數的可見範圍。
- This:函式執行時的上下文。
執行環境僅在任務被呼叫時,才會推入執行堆疊(遵循 LIFO 原則)。
範例
在 first() 呼叫過程中,堆疊會依序存放 Global、First 與 Second 的執行環境,並在任務完成後逐一彈出。
- Global 執行環境 儲存背景資訊,例如
myName="Rick"、first及second的程式碼。 - First 執行環境 儲存函式內的資料,例如
a=1、b=未定義(呼叫 second 函式前)、c=未定義(計算前)。 - Second 執行環境 儲存
arguments=[2,3]及d=4的資訊。
執行環境 (EC) 的堆疊方式
執行環境在執行堆疊中的堆疊順序依照程式執行的順序進行。當任務執行完畢後,其執行環境會被移出堆疊。以下範例展示了此過程:

執行堆疊遵循 LIFO(Last In, First Out)原則。
JavaScript Runtime
JavaScript 程式碼的執行需要 JavaScript 引擎的支援。現代 JavaScript 引擎(如 Chrome 的 V8)會將程式碼解析、編譯並執行,過程中使用執行堆疊 (Execution Stack) 來管理函式調用的順序與執行環境。
除了 JavaScript 引擎,完整的 JavaScript Runtime 還包括:
- Web APIs:提供與 DOM、計時器函式及網路請求等功能
- Callback Queue:儲存等待執行的回調函式
- Event Loop:協調同步與非同步任務的執行
執行流程
以下範例說明 Runtime 的運作流程:
- JavaScript 引擎處理程式碼時採單線程方式,依序執行。
- 當觸發事件(如
onClick)時,事件的 callback function 會進入 Callback Queue。 - 當執行堆疊中的任務全數執行完畢後,事件迴圈 (Event Loop) 會將 callback function 推入執行堆疊中執行。

NOTE: DOM 操作、計時器函式等功能並非 JavaScript 的內建能力,而是由瀏覽器提供的 Web APIs 支援。
Event Loop 與非同步運作機制
Event Loop 是一種機制,用於協調執行堆疊(Execution Stack / Call Stack)、Web APIs 和任務佇列(Callback Queue)之間的工作流程。它確保非同步操作得以有序執行。JavaScript 為單執行緒語言,因此需要 Event Loop 來協調同步與非同步任務。其主要組成為:
- 執行堆疊(Call Stack):執行同步程式碼。
- Web APIs:瀏覽器提供的功能,如
setTimeout、DOM 操作、HTTP 請求。 - 任務佇列(Callback Queue):
- Task Queue:包含
setTimeout、setInterval、HTTP 請求等。 - Micro Task Queue:包含
Promise.then()、MutationObserver等。
- Task Queue:包含
Task Queue vs Micro Task Queue 分類
Task Queue(宏任務佇列)
屬於 Task Queue 的非同步操作包括:
setTimeout()/setInterval()setImmediate()(Node.js)- I/O 操作
- UI 渲染事件
- 使用者互動事件(
click、scroll等) - HTTP 請求回調
Micro Task Queue(微任務佇列)
屬於 Micro Task Queue 的非同步操作包括:
Promise.then()/Promise.catch()/Promise.finally()async/awaitqueueMicrotask()MutationObserverprocess.nextTick()(Node.js)
執行優先級規則
Micro Task Queue 的執行優先於 Task Queue,具體規則為:
-
每次 Event Loop 循環:
- 執行一個 Task Queue 中的任務
- 立即執行所有 Micro Task Queue 中的任務
- 重複上述過程
-
優先級順序:
- 同步程式碼 > Micro Task Queue > Task Queue
Micro Task 在執行時會先清空整個佇列,之後才會繼續執行下一個 Task。這意味著,如果 Micro Task 的數量過多,Task Queue 中的任務就會被延遲執行。
Event Loop 的工作流程
- 執行同步程式碼 → 在 Call Stack 內完成。
- 將非同步任務交給 Web APIs → 例如
setTimeout。 - 完成後推入 Callback Queue → 依照任務類型放入 Task Queue 或 Micro Task Queue。
- Event Loop 檢查 Stack 狀態 → 當堆疊清空後,先處理 Micro Task Queue,再處理 Task Queue。
範例
輸出順序:
進階範例:Micro Task 清空佇列的特性
輸出順序:
關鍵觀察:
- 所有同步程式碼先執行 (
1: Start,7: End) - 所有 Micro Task 必須完全清空才會執行第一個 Task
- 即使在 Micro Task 執行過程中新增的 Micro Task (
5: Micro Task 2) 也會在同一輪中執行 - Task Queue 中的任務 (
2: Task 1,3: Task 2) 被延遲到最後執行