目錄
JavaScript 執行機制與事件循環
一、同步與異步代碼
1. 同步代碼(Synchronous Code)
2. 異步代碼(Asynchronous Code)
二、事件循環(Event Loop)
1. 核心組成
2.?事件循環基本流程
3. 運行機制
三、異步操作分類
1. 任務類型對比
2.?宏任務(Macrotasks)
3.?微任務(Microtasks)
4. 執行順序規則
5. 代碼執行順序示例
6. 常見問題
四、Promise的同步異步問題
五、Node.js 與瀏覽器的事件循環差異
六、關鍵總結
示例
JavaScript 是單線程語言,但其通過?事件循環(Event Loop)?和?任務隊列(Task Queue)?實現了非阻塞異步執行。
一、同步與異步代碼
1. 同步代碼(Synchronous Code)
-
特點:
-
順序執行,阻塞后續代碼
-
直接在主線程(調用棧)執行
-
典型場景:普通函數調用、數學運算
console.log('Start'); let sum = 0; for (let i = 0; i < 1e6; i++) sum += i; console.log('End'); // 必須等待循環執行完
-
2. 異步代碼(Asynchronous Code)
-
特點:
-
非阻塞執行,后續代碼無需等待
-
通過任務隊列管理
-
典型場景:
setTimeout
、fetch
、Promise
、DOM事件
console.log('Start'); setTimeout(() => console.log('Timeout'), 0); console.log('End'); // 輸出順序:Start → End → Timeout
-
二、事件循環(Event Loop)
1. 核心組成
組件 | 作用 |
---|---|
調用棧 (Call Stack) | 存放同步執行代碼(LIFO結構) |
任務隊列 (Task Queue) | 存放待處理的異步任務 |
Web APIs | 瀏覽器提供的異步API(如DOM、定時器) |
2.?事件循環基本流程
事件循環是 JavaScript 處理異步代碼的核心機制,其基本流程如下:
-
執行同步代碼
先執行當前調用棧中的所有同步任務(如函數調用、變量賦值)。 -
處理微任務隊列
同步代碼執行完畢后,立即清空微任務隊列(Microtask Queue)中的所有任務(如?Promise.then
)。 -
渲染頁面(如有需要)
執行 UI 渲染(布局、繪制),但瀏覽器會智能合并渲染操作以優化性能。 -
處理宏任務隊列
從宏任務隊列(Macrotask Queue)中取出一個任務執行,回到步驟 1 開始新的事件循環。
3. 運行機制
st=>start: 開始執行
op1=>operation: 執行同步代碼
op2=>operation: 遇到異步任務
op3=>operation: 注冊到Web APIs
op4=>operation: Web API完成,回調推入任務隊列
cond=>condition: 調用棧是否為空?
op5=>operation: 取出隊列首個任務推入調用棧
e=>end: 結束st->op1->op2->op3->op4->cond
cond(yes)->op5->cond
cond(no)->e
三、異步操作分類
1. 任務類型對比
特性 | 宏任務(Macrotask) | 微任務(Microtask) |
---|---|---|
常見類型 | setTimeout 、setInterval 、I/O操作、UI渲染 | Promise.then 、MutationObserver 、process.nextTick (Node.js) |
執行時機 | 每輪事件循環執行一個宏任務 | 當前宏任務執行完畢后立即執行所有微任務 |
優先級 | 低 | 高 |
2.?宏任務(Macrotasks)
-
常見類型:
-
setTimeout
?/?setInterval
-
I/O 操作(文件讀寫、網絡請求)
-
UI 渲染(瀏覽器)
-
requestAnimationFrame
(瀏覽器)
-
-
特點:每次事件循環處理一個宏任務。
-
微任務優先級高于宏任務:每執行完一個宏任務后,會立即清空所有微任務。
3.?微任務(Microtasks)
-
常見類型:
-
Promise.then
?/?catch
?/?finally
-
MutationObserver
(瀏覽器) -
queueMicrotask
-
-
特點:在當前宏任務結束后、下一個宏任務開始前執行所有微任務。
-
微任務可嵌套:若在微任務中生成新的微任務,新微任務會在當前事件循環中被執行。
4. 執行順序規則
-
執行當前宏任務中的同步代碼
-
執行該宏任務產生的所有微任務
-
執行下一個宏任務
-
循環往復(每次循環稱為一個"tick")
5. 代碼執行順序示例
console.log('Start');setTimeout(() => {console.log('Timeout');
}, 0);Promise.resolve().then(() => console.log('Promise 1')).then(() => console.log('Promise 2'));console.log('End');
輸出順序:
Start
End
Promise 1
Promise 2
Timeout
執行步驟:
-
同步代碼依次執行,輸出?
Start
?和?End
。 -
setTimeout
?回調進入宏任務隊列。 -
Promise.then
?回調進入微任務隊列。 -
同步代碼執行完畢,執行所有微任務(
Promise 1
、Promise 2
)。 -
執行下一個宏任務(
Timeout
)。
6. 常見問題
-
為什么微任務優先級高?
微任務通常用于更緊急的更新(如 Promise 狀態變更),確保在渲染前完成數據更新,提升用戶體驗。 -
如何避免微任務饑餓?
避免在微任務中無限遞歸添加微任務,否則宏任務無法執行,導致頁面卡死。 -
requestAnimationFrame
?是宏任務還是微任務?
它屬于渲染階段的宏任務,用于在下次重繪前執行動畫更新,優先級高于普通宏任務(如?setTimeout
)。
四、Promise的同步異步問題
1. 同步執行:Promise 的構造函數和其執行器函數中的代碼是同步執行的。
2. 異步回調:.then()
、.catch()
?等回調函數是異步的微任務,會在當前同步代碼執行完畢后執行。
3. 示例
console.log('1. 同步代碼開始');const promise = new Promise((resolve) => {console.log('2. Promise 執行器函數(同步)');resolve('resolve 的值');
});promise.then((value) => {console.log('4. .then 回調(異步微任務):', value);
});console.log('3. 同步代碼結束');// 輸出順序:
// 1. 同步代碼開始
// 2. Promise 執行器函數(同步)
// 3. 同步代碼結束
// 4. .then 回調(異步微任務): resolve 的值
4. 異步操作的嵌套
console.log('1. 同步代碼開始');const promise = new Promise((resolve) => {console.log('2. Promise 執行器函數(同步)');setTimeout(() => {console.log('5. setTimeout 回調(異步宏任務)');resolve('resolve 的值');}, 0);
});promise.then((value) => {console.log('6. .then 回調(異步微任務):', value);
});console.log('3. 同步代碼結束');// 輸出順序:
// 1. 同步代碼開始
// 2. Promise 執行器函數(同步)
// 3. 同步代碼結束
// 5. setTimeout 回調(異步宏任務)
// 6. .then 回調(異步微任務): resolve 的值
五、Node.js 與瀏覽器的事件循環差異
特性 | 瀏覽器 | Node.js |
---|---|---|
微任務類型 | Promise 、MutationObserver | Promise 、process.nextTick |
微任務優先級 | 同層級按注冊順序 | process.nextTick ?優先級最高 |
宏任務分層 | 單層任務隊列 | 多階段分層(timers → pending → poll → check → close) |
六、關鍵總結
-
執行順序鐵律:
同步代碼 → 微任務 → 宏任務 → 渲染(瀏覽器) -
微任務優先:
每個宏任務執行完畢后,必須清空所有微任務隊列 -
任務嵌套規則:
-
微任務中產生的微任務會繼續在當前批次執行
-
宏任務中產生的任務會進入下一輪循環
-
-
性能優化建議:
-
避免在微任務中進行耗時操作
-
合理分配任務類型(密集計算使用宏任務分片)
-
問答:
什么是事件循環?
?答案?:執行代碼和收集異步任務,在調用棧空閑時,反復調用任務隊列里回調函數執行機制。為什么有事件循環?
?答案?:JavaScript 是單線程的,為了不阻塞 JS 引擎,設計執行代碼的模型。JavaScript 內代碼如何執行?
?答案?:執行同步代碼,遇到異步代碼交給宿主瀏覽器環境執行 異步有了結果后,把回調函數放入任務隊列排隊 當調用棧空閑后,反復調用任務隊列里的回調函數。什么是宏任務?
?答案?:瀏覽器執行的異步代碼,例如:JS 執行腳本事件,setTimeout/setInterval,AJAX請求完成事件,用戶交互事件等。什么是微任務?
?答案?:JS 引擎執行的異步代碼,例如:Promise對象.then()的回調。
示例
回答下面代碼執行順序:
console.log(1)
setTimeout(() => {console.log(2)const p = new Promise(resolve => resolve(3))p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {setTimeout(() => {console.log(4)}, 0)resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
輸出結果:?
1
7
5
6
2
3
4