? 寫在前面:你不是不懂,是沒真正吃透
事件循環(huán) 幾乎是前端人繞不過的坎,但也總是似懂非懂。
你可能已經(jīng):
- 看了 5 張不同的流程圖
- 刷了 3 遍教學(xué)視頻
- 背過“宏任務(wù)和微任務(wù)”規(guī)則
但真正遇到面試題,比如:
Promise.then
和 setTimeout
誰先執(zhí)行? async/await
究竟屬于哪類任務(wù)? process.nextTick
和 queueMicrotask
誰優(yōu)先?
腦子還是“嗡”的一下。
這篇文章就是為了讓你 真正理解,而不是記答案。
?? 一句話解釋事件循環(huán):JS 是單線程,但支持異步
JavaScript 是 單線程運(yùn)行時(shí):一次只能執(zhí)行一個(gè)任務(wù)。
但我們又能寫異步邏輯:比如定時(shí)器、網(wǎng)絡(luò)請(qǐng)求、用戶操作。JS 怎么做到“非阻塞”呢?
答案就是:事件循環(huán)機(jī)制(Event Loop)
那它是怎樣執(zhí)行的呢?
JS 引擎從任務(wù)隊(duì)列中不斷地調(diào)度任務(wù) —— 執(zhí)行一個(gè)宏任務(wù) → 清空微任務(wù) → 繼續(xù)下一輪宏任務(wù)。
- 主線程:JS 的唯一運(yùn)行通道,只能同時(shí)跑一個(gè)任務(wù)。
- 執(zhí)行棧:任務(wù)正在執(zhí)行時(shí),代碼的“調(diào)用路徑”。
- 任務(wù)隊(duì)列:未來要執(zhí)行的代碼排隊(duì)等待。
主線程中,存在:
- 同步任務(wù):立即執(zhí)行的代碼(如
console.log()
) - 異步任務(wù):不會(huì)立即執(zhí)行,注冊(cè)后進(jìn)入 任務(wù)隊(duì)列 等待,由瀏覽器安排時(shí)間再執(zhí)行(如
setTimeout()
)
所有異步任務(wù)都要依賴事件循環(huán)調(diào)度!
一圖看懂 JS 執(zhí)行順序:

那么哪些是宏任務(wù)、哪些又是微任務(wù)呢?
?? 宏任務(wù) vs 微任務(wù)
類型 | 常見任務(wù)來源 | 什么時(shí)候執(zhí)行 |
---|
宏任務(wù) | setTimeout 、setInterval 、UI 渲染 、MessageChannel | 一輪事件循環(huán)的起點(diǎn) |
微任務(wù) | Promise.then 、queueMicrotask 、MutationObserver | 當(dāng)前宏任務(wù)執(zhí)行完立即執(zhí)行 |
記?。?span style="font-weight: 700; color: rgb(33, 150, 243);">一個(gè)宏任務(wù)執(zhí)行完 → 清空所有微任務(wù) → 執(zhí)行下一個(gè)宏任務(wù)
?? 那 Web Worker 屬于什么任務(wù)?
直接給結(jié)論,Web Worker 屬于獨(dú)立線程運(yùn)行,不占用主線程時(shí)間。
但是需要注意:
Worker發(fā)回的消息會(huì)被主線程當(dāng)作 普通的宏任務(wù) 處理(和setTimeout
同級(jí))
console.log("1. 開始炒菜");
const worker = new Worker("worker.js");
worker.postMessage("切土豆絲");
worker.onmessage = (e) => {
console.log("4. 收到切好的土豆絲");
};
console.log("2. 炒菜中...");
setTimeout(() => { console.log("3. 定時(shí)器任務(wù)"); }, 0);
self.onmessage = (e) => {
for (let i=0; i<1e9; i++) {}
self.postMessage("切好了");
};
輸出順序: 1 → 2 → 3 → 4(Worker計(jì)算耗時(shí),但主線程不等待,先執(zhí)行其他宏任務(wù))
?? 舉幾個(gè)栗子徹底理解輸出順序
1. 來個(gè)簡(jiǎn)單的練練手
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
輸出順序: 1 → 4 → 3 → 2
執(zhí)行過程:
- 執(zhí)行同步任務(wù):
console.log(1)
- 注冊(cè)
setTimeout
(宏任務(wù)),加入任務(wù)隊(duì)列 Promise.then()
加入微任務(wù)隊(duì)列- 執(zhí)行同步任務(wù):
console.log(4)
- 清空微任務(wù):輸出
3
- 下一輪宏任務(wù):輸出
2
2. async/await 屬于什么任務(wù)?
async function test() {
console.log('a');
await Promise.resolve();
console.log('b');
}
test();
console.log('c');
輸出順序: a → c → b
拆解說明:
await
會(huì)將 console.log('b')
拆成一個(gè)微任務(wù)- 所以整個(gè)
async
函數(shù)執(zhí)行到 await
暫停,主線程繼續(xù)跑 console.log('c')
- 主線程執(zhí)行完后 → 微任務(wù)觸發(fā) → 輸出
b
3. 來道高頻面試題試試手
先不看答案自己嘗試解答
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log('promise1');
return Promise.resolve();
})
.then(() => {
console.log('promise2');
});
queueMicrotask(() => {
console.log('queueMicrotask');
});
console.log('script end');
拆解過程:
- 先執(zhí)行同步任務(wù)
console.log('script start')
- 注冊(cè)
setTimeout
(宏任務(wù)),加入任務(wù)隊(duì)列 Promise.then()
加入微任務(wù)隊(duì)列(promise1
)promise2
屬于 promise1
微任務(wù)執(zhí)行完成后才執(zhí)行,所以先觀察下面還有沒有別的微任務(wù)queueMicrotask
加入微任務(wù)隊(duì)列- 執(zhí)行同步任務(wù)
console.log('script end')
執(zhí)行結(jié)果:
script start
script end
promise1
queueMicrotask
promise2
setTimeout
你有沒有答對(duì)呢...
?? 面試怎么答才顯得專業(yè)?
? 面試官:“你能解釋下 JS 的事件循環(huán)嗎?”
你可以這樣答:
- 事件循環(huán)是 JS 控制異步執(zhí)行順序的機(jī)制。
- 所有任務(wù)分為同步任務(wù)和異步任務(wù)。異步任務(wù)進(jìn)一步劃分為宏任務(wù)和微任務(wù)。
- JS 每一輪事件循環(huán)會(huì)執(zhí)行一個(gè)宏任務(wù),執(zhí)行完后清空所有微任務(wù)。
- 微任務(wù)優(yōu)先于下一輪宏任務(wù)執(zhí)行,所以 Promise.then 總是先于 setTimeout。
- async/await 本質(zhì)上是 Promise 的語法糖,會(huì)拆成兩個(gè)微任務(wù)。
下次在面試中再被問到就應(yīng)該 自信吟唱 了。??
?? 小結(jié):寫給自己復(fù)習(xí)用
知識(shí)點(diǎn) | 要點(diǎn) |
---|
事件循環(huán)機(jī)制 | 宏任務(wù) → 微任務(wù) → 下一輪宏任務(wù) |
同步任務(wù) | 立即執(zhí)行的代碼 |
異步任務(wù) | 注冊(cè)進(jìn)隊(duì)列等待(分宏/微任務(wù)) |
微任務(wù) | Promise.then 、queueMicrotask ,總是在宏任務(wù)后立即執(zhí)行 |
async/await | 會(huì)被拆成 .then() 形成的微任務(wù) |
Web Worker | 提供多線程能力,但與主線程隔離,靠 postMessage 通信 |
面試答法 | 清晰區(qū)分同步/異步 + 宏/微任務(wù) + 舉例輸出順序 |
轉(zhuǎn)自https://juejin.cn/post/7501880110837907508
該文章在 2025/5/13 14:09:05 編輯過