Event Loop:
單執行緒的異步奇蹟
為什麼 JS 只有一個執行緒卻不會卡死?深入理解 Call Stack、Task Queue 與 Microtasks 的協作模式,掌握前端效能優化的核心。
痛點:為什麼我的按鈕沒反應?
你是否遇過這樣的情況:網頁在執行一段複雜的運算時,點擊按鈕沒有反應、動畫卡住、甚至瀏覽器跳出「網頁沒有回應」? 這是因為 JavaScript 是單執行緒 (Single-threaded)。
JS 引擎的限制
JS 引擎(如 Chrome 的 V8)只有一個 **Call Stack**。同一時間只能做一件事。 如果你在 Stack 裡塞進一個執行 5 秒鐘的 `while(true)`,那這 5 秒內,瀏覽器完全無法處理點擊事件或更新畫面。
// 🚫 阻塞主執行緒的罪魁禍首
while (isComputing) {
// 做極其複雜的數學運算...
}
一、視覺化 Event Loop:四大主角
為了解決單執行緒的阻塞問題,JS 環境(瀏覽器或 Node.js)提供了一套機制。讓我們看看這四個組件是如何運作的:
Call Stack
正在執行中的同步程式碼。
Web APIs
處理計時器、DOM 事件、AJAX。
Microtasks
Promise.then、MutationObserver (優先級高)。
Task Queue
setTimeout、setInterval (優先級低)。
Event Loop 的工作只有一件事:
「如果 Call Stack 是空的,就去 Microtask Queue 搬東西過來跑;如果 Microtask 也空了,再去 Task Queue 搬東西。」
二、微任務 vs 宏任務:誰先執行?
這是最經典的面試題。你需要記住:微任務 (Microtasks) 永遠插隊在宏任務 (Macrotasks) 之前。
Macrotasks (宏任務)
- setTimeout / setInterval
- setImmediate (Node.js)
- I/O 操作
- UI 渲染 (Rendering)
Microtasks (微任務)
- Promise.then / .catch / .finally
- Async / Await (本質是 Promise)
- process.nextTick (Node.js 優先級最高)
- MutationObserver
測驗:這段程式碼會印出什麼?
答案:1 -> 4 -> 3 -> 2
解析:1 和 4 是同步程式碼先跑。3 是微任務,在同步跑完後立即執行。2 是宏任務,排在最後。
三、Async / Await:優雅的包裝
`async/await` 讓異步看起來像同步。但它背後依然是 Promise。當執行到 `await` 時,函式會**暫停**,交出執行權,並把剩餘的部分塞進 Microtask Queue。
四、效能實戰:不要阻塞渲染
瀏覽器通常每 16.7 毫秒 (60fps) 會進行一次 **Render (渲染)**。如果你的 JS 執行時間太長,超出了這個視窗,使用者就會感覺到掉幀。
壞習慣:長任務
一次性處理 10 萬筆資料,導致 Stack 一直是滿的,瀏覽器無法進行 Render。
好習慣:任務切分
使用 `setTimeout` 或 `requestIdleCallback` 將大任務拆成小碎片,讓 Render 有機會插隊進來。
總結:理解規則,優化體驗
Event Loop 是 JavaScript 的「調度員」。理解它的運作邏輯,不僅能幫你通過面試,更能讓你在面對複雜的異步邏輯與效能瓶頸時,清楚知道問題出在哪裡,並寫出更流暢、更穩定的前端應用。