TL;DR | 面試情境模擬 #
👴 面試官:JavaScript 的 Event Loop 是什麼?
🧑💻 你:JS 是單執行緒,一次只能做一件事。Event Loop 是它處理非同步的機制:同步程式碼在 Call Stack 上執行;setTimeout 等非同步操作的回呼放在 Task Queue;Promise 的 .then 放在 Microtask Queue。Call Stack 清空後,Event Loop 先清空所有 Microtask Queue,再取一個 Task Queue 的任務,如此循環。所以 Promise 的 .then 永遠比 setTimeout 先執行。
比喻:餐廳廚師 #
只有一個廚師(單執行緒):
- Call Stack:廚師現在正在做的菜(同步執行)
- Web APIs:計時器、網路請求等在廚師旁邊等待中的準備工作
- Microtask Queue:貴賓(Promise)點的菜,廚師空了立刻做
- Task Queue:普通客人(setTimeout)排的號碼牌,貴賓都服務完才輪到
執行環境 #
┌─────────────────────────────────┐
│ Call Stack │ ← 同步執行
│ [ main() ] │
│ [ console.log() ] │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Web APIs │ ← 瀏覽器/Node.js 提供
│ setTimeout / fetch / DOM Events│
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Microtask Queue │ ← Promise.then, queueMicrotask
│ [ then1, then2, ... ] │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Task Queue │ ← setTimeout, setInterval, I/O
│ [ cb1, cb2, ... ] │
└─────────────────────────────────┘
Event Loop 規則:
- Call Stack 清空
- 清空所有 Microtask Queue
- 取 Task Queue 的第一個任務
- 重複
執行順序範例 #
console.log('1') // 同步
setTimeout(() => {
console.log('2') // Task Queue
}, 0)
Promise.resolve().then(() => {
console.log('3') // Microtask Queue
})
console.log('4') // 同步
// 輸出順序:1, 4, 3, 2
為什麼是 1, 4, 3, 2? #
1. console.log('1') 進 Stack,執行,輸出 1
2. setTimeout 進 Web APIs,計時 0ms 後把回呼放入 Task Queue
3. Promise.then 把回呼放入 Microtask Queue
4. console.log('4') 進 Stack,執行,輸出 4
5. Stack 清空 → 先清 Microtask Queue → 輸出 3
6. 再取 Task Queue → 輸出 2
async/await 底層 #
async/await 是 Promise 的語法糖:
async function foo() {
console.log('A')
await bar() // 等同於 Promise.then
console.log('C') // 這行放進 Microtask Queue
}
console.log('B')
// 輸出:B, A, C
await 之後的程式碼,行為等同於 .then() 的回呼,放入 Microtask Queue 等 Stack 清空才執行。
💡 面試官可能會追問 #
Q1:為什麼 JavaScript 是單執行緒? #
設計之初是為了操作 DOM,多執行緒並行修改 DOM 會產生複雜的競爭條件問題。單執行緒讓 DOM 操作天然安全。需要 CPU 密集任務可以用 Web Worker(獨立執行緒,不能操作 DOM)。
Q2:Node.js 的 Event Loop 和瀏覽器一樣嗎? #
結構類似,但 Node.js 的 Event Loop 有更多階段(Timers、I/O callbacks、Idle、Poll、Check、Close callbacks),並且有 setImmediate 和 process.nextTick(比 Microtask 更早)的差異。
Q3:setTimeout(fn, 0) 和 Promise.resolve().then(fn) 哪個先執行? #
Promise.resolve().then(fn) 先執行,因為它在 Microtask Queue;setTimeout 在 Task Queue。無論 delay 設為 0,Task Queue 都在 Microtask Queue 之後處理。