JavaScript 事件循環機制(Event Loop)詳解
JavaScript 是 單線程、非阻塞 語言,依賴 事件循環(Event Loop) 來實現異步編程。它的執行模型包括 調用棧(Call Stack)、任務隊列(Task Queue)和微任務隊列(Microtask Queue)。
1. 事件循環(Event Loop)基本流程
事件循環的核心工作方式:
- 同步任務(Synchronous) 進入 調用棧 依次執行。
- 遇到 異步任務(如 setTimeout、Promise、I/O 操作),交給 Web API(如瀏覽器、Node.js 運行時)處理,并繼續執行同步代碼。
- 同步代碼執行完畢,調用棧清空,事件循環檢查 微任務隊列(Microtask Queue),依次執行所有微任務。
- 微任務執行完畢,進入 宏任務隊列(Macro Task Queue),取出第一個任務執行。
- 重復以上步驟。
2. 任務類型:同步任務 vs 異步任務
JavaScript 任務分為:
(1) 同步任務(Synchronous)
- 直接執行,放入 調用棧(Call Stack)
- 例子:
console.log('同步任務1'); // 立即執行
(2) 異步任務(Asynchronous)
- 由 Web API 處理,待合適時機進入任務隊列:
- 宏任務(Macro Task)
- 微任務(Micro Task)
3. 宏任務(Macro Task) vs 微任務(Micro Task)
任務類型 | 常見API | 進入隊列 |
---|---|---|
宏任務(Macro Task) | setTimeout 、setInterval 、setImmediate (Node.js)、I/O 、UI渲染 | 任務隊列(Task Queue) |
微任務(Micro Task) | Promise.then() 、queueMicrotask() 、MutationObserver 、process.nextTick() (Node.js) | 微任務隊列(Microtask Queue) |
4. 事件循環執行流程
console.log('同步任務1');setTimeout(() => {console.log('宏任務1');
}, 0);Promise.resolve().then(() => {console.log('微任務1');
});console.log('同步任務2');
執行順序解析
console.log('同步任務1')
執行(同步任務)setTimeout()
放入 宏任務隊列,等待執行Promise.then()
放入 微任務隊列console.log('同步任務2')
執行(同步任務)- 調用棧清空,檢查 微任務隊列,執行
console.log('微任務1')
- 微任務清空后,執行 宏任務隊列,輸出
console.log('宏任務1')
最終輸出順序
同步任務1
同步任務2
微任務1
宏任務1
5. setTimeout(fn, 0)
為什么不立即執行?
setTimeout(fn, 0)
也會進入 宏任務隊列,需要等當前同步任務執行完畢,并在 微任務全部執行完畢后,才能執行。- 示例
執行順序setTimeout(() => console.log('宏任務'), 0); Promise.resolve().then(() => console.log('微任務')); console.log('同步任務');
同步任務 微任務 宏任務
6. Promise
和 setTimeout
誰先執行?
Promise.then()
是微任務,會先執行setTimeout()
是宏任務,等微任務執行完才執行
示例
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
執行順序
Promise
setTimeout
7. 事件循環完整示例
console.log('start');setTimeout(() => {console.log('setTimeout');
}, 0);Promise.resolve().then(() => {console.log('Promise1');
}).then(() => {console.log('Promise2');
});console.log('end');
執行順序
console.log('start')
(同步任務)setTimeout()
進入 宏任務隊列Promise.then()
進入 微任務隊列console.log('end')
(同步任務)- 同步任務結束,執行微任務
Promise1
Promise2
- 微任務執行完畢,執行宏任務
setTimeout
最終輸出:
start
end
Promise1
Promise2
setTimeout
8. async/await
也是微任務
示例
async function asyncFunc() {console.log('A');await Promise.resolve();console.log('B');
}console.log('C');
asyncFunc();
console.log('D');
執行順序
C
A
D
B
解釋
console.log('C')
(同步任務)- 調用
asyncFunc()
,輸出A
await Promise.resolve()
讓console.log('B')
進入 微任務隊列console.log('D')
(同步任務)- 執行微任務
console.log('B')
9. setTimeout()
和 setImmediate()
(Node.js)
在 Node.js 中:
setTimeout(fn, 0)
進入 定時器隊列setImmediate(fn)
進入 Check 隊列setImmediate()
通常比setTimeout(0)
先執行
示例
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
輸出順序(Node.js)
setImmediate
setTimeout
10. 關鍵點總結
概念 | 說明 |
---|---|
同步任務 | 立即執行,進入 調用棧 |
異步任務 | 由 Web API 處理,稍后執行 |
宏任務(Macro Task) | setTimeout 、setInterval 、setImmediate 、I/O |
微任務(Micro Task) | Promise.then() 、queueMicrotask() 、MutationObserver |
事件循環(Event Loop) | 先執行同步任務 → 再執行微任務 → 再執行宏任務 |
11. 最佳實踐
? 避免阻塞主線程
- 使用
setTimeout(fn, 0)
或requestIdleCallback(fn)
處理密集計算
? 優先使用微任務優化異步流程
Promise.then()
比setTimeout()
先執行
? 了解 async/await
也是微任務
await
后的代碼會在微任務隊列中執行
總結
- JavaScript 是單線程,使用事件循環管理異步任務
- 任務分為:
- 同步任務(調用棧直接執行)
- 異步任務(進入宏任務/微任務隊列)
- 執行順序:
- 先執行同步任務
- 再執行所有微任務
- 最后執行宏任務
Promise.then()
比setTimeout()
先執行async/await
本質上是 Promise,屬于微任務
這些概念對于理解 JavaScript 的異步執行至關重要!🚀