> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# JavaScript 工程師的修行之路（1）：深入理解 Event Loop、Call Stack 與非同步機制
- URL: https://blog.anthea.day/series-javascript-journey-1/
- Published: 2025-03-09T12:58:52.000Z
- Updated: 2026-11-07T20:07:50.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, JavaScript 工程師的修行之路

剛接觸 JavaScript 的時候不知道大家有沒有看過這個情境：

```javascript
console.log("A"); 
 
setTimeout(() => { 
  console.log("B"); 
}, 0); 
 
console.log("C");
```

你覺得跑完上面的程式碼後，最後 log 印出來的順序是什麼呢？

如果你的答案是 `A -> B -> C` 的話，歡迎你來到我的文章！希望這篇文章可以幫助你更理解 JavaScript 的程式執行順序，理解為什麼這段程式碼跑完後，順序會是 `A -> C -> B` 。

在了解執行順序之前，我們需要先認識 JavaScript 是如何處理程式碼的。

### 同步與非同步的概念

在 JavaScript 中，程式碼的執行方式可以分為**同步（Synchronous）**和**非同步（Asynchronous）**兩種：

- 同步程式碼（Synchronous）：程式碼會按照撰寫的順序逐行執行，當一個函式正在執行時，其他程式碼必須等它執行完畢後才能繼續。
- 非同步程式碼（Asynchronous）：某些操作可能會花費較長的時間（例如 I/O 操作、網路請求、計時器等），這些操作不會阻塞主執行緒，而是會在背景執行，等到完成後才通知 JavaScript 執行對應的回調函式。

想像你去一家早餐店點餐，同步就是你排隊點了一份三明治，然後站在櫃檯前等老闆做好才離開。這時候老闆只能處理你的訂單，無法同時幫其他客人做餐點。如果餐點需要 5 分鐘，所有其他客人都得等你先拿到才輪到他們。

而非同步則是你先點了三明治，老闆告訴你「等一下，我做好會叫你」，然後繼續幫其他客人點餐。當你的三明治做好時，老闆才喊你的號碼，你再過去拿餐。這個情境下，其他客人不需要等你拿到餐點，大家的服務變得更有效率。

在 JavaScript 中：

- 同步程式碼 就像「站在櫃檯等老闆做好三明治」，所有程式碼會按照順序執行，直到當前的執行完成，才能繼續下一步。
- 非同步程式碼 則像「點完餐後先去做別的事，等三明治做好了再來拿」，這樣 JavaScript 可以處理更多請求，而不會因為等待某個結果而卡住。

### Call Stack（呼叫堆疊）

JavaScript 採用**單線程（single-threaded）**模型，代表它一次只能執行一個任務。如果有多個任務要執行時，這些任務們就必須要排隊，等待 JavaScript 執行到他們。  
那這些任務會在哪裡排隊呢？通常來說，同步程式碼都會被放到一個叫 **Call Stack（呼叫堆疊）**的地方等待被執行。舉一個簡單的例子來說：

```cpp
console.log("A"); 
console.log("B");
```

這時候程式碼的運作會像是這樣：

![](https://blog.anthea.day/content/images/2026/10/1-eo0i_iowx62qxvfw4uv5rg.png)

如果程式碼都是這樣執行的話，為什麼一開始的例子，`setTimeout` 裡的內容會是最後才出現呢？

### Task Queue（任務佇列）

這是因為非同步的程式碼排隊的地方和同步的程式碼不同。非同步的程式碼通常會有一個 callback function，而他會被放到 **Task Queue（任務佇列）** 裡等待，當 Call Stack 沒有任務要執行時，他才會被取出來，放到 Call Stack 裡被執行。舉例來說：

```javascript
console.log("A"); 
 
setTimeout(() => { 
  console.log("B"); 
}, 0); 
 
setTimeout(() => { 
  console.log("C"); 
}, 0); 
 
console.log("D");
```

![](https://blog.anthea.day/content/images/2026/10/1-ibwmgrvyw09cilm1rcvveg.png)

那誰會負責處理 Call Stack 裡面的任務？又是誰負責把 Task Queue 裡面的任務移動到 Call Stack 內讓他們被執行的？

### Event Loop（事件迴圈）

**Event Loop（事件迴圈）**是 JavaScript 的核心機制之一，負責監控 Call Stack 和 Task Queue，確保非同步程式碼能夠正確執行。

而 Event Loop 會幫忙做這些事情：

1. 先執行 Call Stack 裡的所有同步程式碼。
2. 如果 Call Stack 清空了，Event Loop 會檢查 Microtask Queue（例如 `Promise.then()`），如果有，則執行。
3. 當 Microtask Queue 清空後，Event Loop 會檢查 Task Queue，並將第一個等待的回調函式推入 Call Stack 執行。
4. 重複這個過程，確保所有程式碼依照適當的順序執行。

我們在上面有提到 Call Stack 和 Task Queue，那 Microtask Queue 又是什麼？

### Microtack Queue （微任務佇列）

除了 Task Queue 之外，還有一個優先級別更高的佇列：**Microtack Queue （微任務佇列）**。會在這裡排隊的任務也都是非同步程式碼，在這之中會有我們很常使用到的一個人：`Promise.then()`。一樣讓我們用一個簡單的例子來看一下：

```javascript
console.log("A"); 
 
setTimeout(() => { 
  console.log("B"); 
}, 0); 
 
Promise.resolve().then(() => { 
  console.log("C"); 
}); 
 
console.log("D");
```

![](https://blog.anthea.day/content/images/2026/10/1-wdkhrxlef8xfcn4-5lh8pw.png)

### 總結

讓我們來回顧一下這篇文章我們學到了什麼：

- 同步程式碼 就像在隊伍裡排隊結帳，一行一行地執行，進入 Call Stack，跑完後才換下一個。
- 非同步程式碼（像 `setTimeout`）會被交給 Web API 處理，等它們準備好，才會被放回來，排隊等著執行。
- Microtask Queue（像 `Promise.then()`）的優先級比 Task Queue 高，會先執行，所以 `Promise.then()` 會比 `setTimeout(..., 0)` 先跑。
- Event Loop 就像 JavaScript 的調度員，負責監控 Call Stack 和 Task Queue，確保大家按順序執行，不會亂掉。

那麼現在，你可以理解為什麼這段程式碼被印出來的順序是 `A -> C -> B` 了嗎？😉

```javascript
console.log("A"); 
 
setTimeout(() => { 
  console.log("B"); 
}, 0); 
 
console.log("C");
```