在JavaScript中,微任務(Microtask)和宏任務(Macrotask)是異步任務執行機制的重要組成部分,它們共同構成了JavaScript事件循環(Event Loop)的核心邏輯。理解這兩個概念對于編寫高性能、無阻塞的代碼至關重要。
1. 基本概念
宏任務(Macrotask)
- 定義:由瀏覽器或Node.js環境提供的異步任務,通常是一些離散的、獨立的操作。
- 常見宏任務:
setTimeout
、setInterval
setImmediate
(Node.js)requestAnimationFrame
(瀏覽器)- I/O操作(如文件讀取、網絡請求)
- UI渲染(瀏覽器)
微任務(Microtask)
- 定義:JavaScript引擎自身提供的異步任務,通常是需要在當前任務完成后立即執行的操作。
- 常見微任務:
Promise.then()
、Promise.catch()
、Promise.finally()
MutationObserver
(瀏覽器)process.nextTick
(Node.js)queueMicrotask
(ES2020+)
2. 執行順序規則
JavaScript的事件循環執行機制遵循以下規則:
- 執行棧(Call Stack) 為空時,事件循環開始處理任務隊列。
- 宏任務隊列 每次只取一個任務執行,執行完畢后立即處理 微任務隊列。
- 微任務隊列 會被清空(依次執行所有微任務),直到隊列為空。
- 微任務隊列清空后:
- 瀏覽器可能進行 UI渲染(僅在瀏覽器環境)。
- 事件循環回到步驟2,繼續處理下一個宏任務。
總結:宏任務 → 清空微任務隊列 → UI渲染 → 下一個宏任務
3. 經典示例分析
console.log('1. 主線程開始');// 宏任務1
setTimeout(() => {console.log('2. setTimeout 回調執行');// 微任務1Promise.resolve().then(() => {console.log('3. setTimeout 內部 Promise 微任務執行');});
}, 0);// 微任務2
Promise.resolve().then(() => {console.log('4. 主線程 Promise 微任務執行');// 微任務3queueMicrotask(() => {console.log('5. queueMicrotask 微任務執行');});
});console.log('6. 主線程結束');
執行順序解析:
-
主線程 執行:
- 輸出
1. 主線程開始
setTimeout
被放入 宏任務隊列。Promise.then
被放入 微任務隊列(微任務2)。- 輸出
6. 主線程結束
。
- 輸出
-
微任務隊列 執行:
- 執行微任務2,輸出
4. 主線程 Promise 微任務執行
。 queueMicrotask
被加入微任務隊列(微任務3)。- 繼續執行微任務3,輸出
5. queueMicrotask 微任務執行
。
- 執行微任務2,輸出
-
宏任務隊列 執行:
- 執行宏任務1(
setTimeout
回調),輸出2. setTimeout 回調執行
。 Promise.then
被加入微任務隊列(微任務1)。- 清空微任務隊列,執行微任務1,輸出
3. setTimeout 內部 Promise 微任務執行
。
- 執行宏任務1(
最終輸出順序:
1. 主線程開始
6. 主線程結束
4. 主線程 Promise 微任務執行
5. queueMicrotask 微任務執行
2. setTimeout 回調執行
3. setTimeout 內部 Promise 微任務執行
4. 實際應用場景
4.1 微任務的應用
- Promise鏈式調用:確保回調按順序執行,不阻塞主線程。
fetchData().then(processData).then(updateUI).catch(handleError);
- DOM變更后立即操作:使用
MutationObserver
監聽DOM變化后執行回調。
4.2 宏任務的應用
- 定時器:實現延遲執行或周期性任務。
setTimeout(() => {// 延遲執行的代碼 }, 1000);
- 分批處理大量數據:避免長時間阻塞主線程。
function processLargeData(data) {const batchSize = 1000;let index = 0;function processBatch() {const batch = data.slice(index, index + batchSize);// 處理當前批次數據index += batchSize;if (index < data.length) {setTimeout(processBatch, 0); // 使用宏任務拆分處理}}processBatch(); }
5. 微任務 vs 宏任務的關鍵區別
特性 | 微任務(Microtask) | 宏任務(Macrotask) |
---|---|---|
執行時機 | 當前任務結束后立即執行(在渲染前) | 下一次事件循環開始時執行(可能在渲染后) |
隊列清空規則 | 一次性清空所有微任務 | 每次只處理一個宏任務 |
觸發頻率 | 高(可能在一次事件循環中多次觸發) | 低(每次事件循環只處理一個) |
典型場景 | Promise回調、DOM變更監聽 | 定時器、I/O操作、UI渲染 |
6. 注意事項
- 微任務阻塞渲染:如果微任務隊列過長,會導致UI長時間無法渲染,造成頁面卡頓。
- 合理選擇任務類型:
- 需要立即執行的異步操作(如Promise鏈)使用微任務。
- 需要離散執行的操作(如定時器、大數據處理)使用宏任務。
- 瀏覽器兼容性:
queueMicrotask
是較新的API,舊瀏覽器可能需要使用Promise.resolve().then()
替代。
通過理解微任務和宏任務的執行機制,你可以更精確地控制代碼的執行順序,避免性能問題,提升用戶體驗。