最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深度解析JavaScript中事件循環(huán)(Event?Loop)的執(zhí)行順序

 更新時(shí)間:2026年05月21日 09:27:43   作者:豹哥學(xué)前端  
為什么 setTimeout 明明是 0 毫秒,卻要等到最后才執(zhí)行,這篇文章小編就來和大家詳細(xì)介紹一下JavaScript中事件循環(huán)(Event?Loop)的執(zhí)行機(jī)制吧

為什么 setTimeout 明明是 0 毫秒,卻要等到最后才執(zhí)行?

Promise.thensetTimeout 到底誰先執(zhí)行?

面試官總愛問的“事件循環(huán)”,今天一篇幫你徹底打通。

1. 從一道經(jīng)典面試題說起

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

問:以上代碼的輸出順序是什么?

很多人會(huì)脫口而出:1 2 3 41 4 2 3

正確答案是:1 4 3 2

為什么?這正是事件循環(huán)的規(guī)則導(dǎo)致的。

2. 事件循環(huán)是什么?為什么要它?

JavaScript 是單線程語言,同一時(shí)間只能做一件事。但我們需要處理用戶點(diǎn)擊、網(wǎng)絡(luò)請(qǐng)求、定時(shí)器等異步操作。如果這些操作都排隊(duì)執(zhí)行,那網(wǎng)絡(luò)請(qǐng)求的 200ms 就會(huì)讓整個(gè)頁面卡住。

事件循環(huán)就是 JS 引擎用來調(diào)度同步代碼、異步回調(diào)、I/O 操作的一套機(jī)制,它讓 JS 既能保持單線程,又能高效處理異步。

生活類比

  • 你有一個(gè)“待辦清單”(任務(wù)隊(duì)列)。
  • 你先處理手頭的事(同步代碼)。
  • 手頭的事干完了,就去清單里看看有沒有新的待辦。
  • 如果有,就取出來做,做完再去看清單……
  • 這個(gè)“反復(fù)看清單并取任務(wù)執(zhí)行”的過程,就是事件循環(huán)。

3. 宏任務(wù)(MacroTask)與微任務(wù)(MicroTask)

在事件循環(huán)中,任務(wù)被分為兩種:宏任務(wù)微任務(wù)。它們的執(zhí)行優(yōu)先級(jí)不同。

3.1 宏任務(wù)(MacroTask)

每次從任務(wù)隊(duì)列中取出的一個(gè)任務(wù),稱為一個(gè)宏任務(wù)。

常見宏任務(wù)

  • 整體代碼塊(script)
  • setTimeout、setInterval
  • I/O 操作
  • UI 渲染
  • setImmediate(Node.js)
  • requestAnimationFrame(瀏覽器)

3.2 微任務(wù)(MicroTask)

在當(dāng)前宏任務(wù)執(zhí)行完成后、下一個(gè)宏任務(wù)開始前執(zhí)行的任務(wù)。

常見微任務(wù)

  • Promise.then / catch / finally
  • MutationObserver(瀏覽器)
  • queueMicrotask
  • process.nextTick(Node.js,優(yōu)先級(jí)高于普通微任務(wù))

3.3 優(yōu)先級(jí)總結(jié)

微任務(wù)隊(duì)列 > 宏任務(wù)隊(duì)列

每個(gè)宏任務(wù)執(zhí)行完后,會(huì)立即清空當(dāng)前所有的微任務(wù),然后再去取下一個(gè)宏任務(wù)。

4. 事件循環(huán)的工作流程(附流程圖)

用文字描述:

  1. 執(zhí)行一個(gè)宏任務(wù)(最開始執(zhí)行的是全局腳本代碼)。
  2. 執(zhí)行過程中如果遇到微任務(wù),就把它添加到微任務(wù)隊(duì)列。
  3. 當(dāng)前宏任務(wù)執(zhí)行完畢,檢查微任務(wù)隊(duì)列。
  4. 依次執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù)(直到清空)。
  5. 執(zhí)行必要的 UI 渲染(瀏覽器)。
  6. 從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù),重復(fù)步驟 1。

流程圖(純文本版):

┌─────────────────────────┐
│   開始執(zhí)行宏任務(wù)(script)  │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│  同步代碼執(zhí)行,遇到微任務(wù)入隊(duì) │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│   宏任務(wù)執(zhí)行完畢          │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│   清空當(dāng)前所有微任務(wù)      │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│   (可能執(zhí)行 UI 渲染)    │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│   取出下一個(gè)宏任務(wù)        │
└─────────────────────────┘

5. 用代碼驗(yàn)證每一步

示例 1:基本順序

setTimeout(() => console.log('宏任務(wù)'), 0);
Promise.resolve().then(() => console.log('微任務(wù)'));
console.log('同步');
// 輸出:同步 → 微任務(wù) → 宏任務(wù)

示例 2:微任務(wù)中注冊(cè)新微任務(wù)

Promise.resolve().then(() => {
  console.log('微任務(wù)1');
  Promise.resolve().then(() => console.log('微任務(wù)2'));
});
console.log('同步');
// 輸出:同步 → 微任務(wù)1 → 微任務(wù)2

關(guān)鍵:微任務(wù)隊(duì)列會(huì)一次性清空,新添加的微任務(wù)也會(huì)在當(dāng)前輪次執(zhí)行完。

示例 3:宏任務(wù)中注冊(cè)微任務(wù)

setTimeout(() => {
  console.log('宏任務(wù)');
  Promise.resolve().then(() => console.log('宏任務(wù)中的微任務(wù)'));
}, 0);
Promise.resolve().then(() => console.log('外層微任務(wù)'));
// 輸出:外層微任務(wù) → 宏任務(wù) → 宏任務(wù)中的微任務(wù)

6. async/await 在事件循環(huán)中的特殊表現(xiàn)

async/await 是 Promise 的語法糖,但它在事件循環(huán)中的行為有細(xì)微的陷阱。

async function foo() {
  console.log('2');
  await bar();        // ← 這里 await 后面的代碼相當(dāng)于 Promise.then
  console.log('4');
}
async function bar() {
  console.log('3');
}
console.log('1');
foo();
console.log('5');
// 輸出:1 2 3 5 4

分析

  • 同步:12、3、5
  • await bar() 后面的 console.log('4') 相當(dāng)于 Promise.resolve(bar()).then(() => console.log('4')),因此它進(jìn)入微任務(wù)隊(duì)列。
  • 當(dāng)前宏任務(wù)執(zhí)行完后,清空微任務(wù),輸出 4。

陷阱:很多人以為 await 會(huì)阻塞,其實(shí)它只是把后續(xù)代碼包裝成微任務(wù),并不會(huì)阻塞主線程。

7. Node.js 與瀏覽器事件循環(huán)的區(qū)別

瀏覽器和 Node.js 的事件循環(huán)在實(shí)現(xiàn)上有所不同,下面列出主要差異(Node.js 版本 11+ 后已盡量與瀏覽器對(duì)齊,但仍有一些區(qū)別)。

特性瀏覽器Node.js(v11+)
宏任務(wù)類型setTimeoutsetInterval、I/O、UI 渲染setTimeout、setInterval、I/O、setImmediate、process.nextTick(特殊微任務(wù))
微任務(wù)執(zhí)行時(shí)機(jī)每個(gè)宏任務(wù)之后清空微任務(wù)基本一致,但 process.nextTick 優(yōu)先級(jí)高于 Promise.then
循環(huán)階段簡單:宏任務(wù) → 微任務(wù) → 渲染多階段:timers → pending callbacks → idle → poll → check → close

Node.js 的事件循環(huán)有更多階段,但作為前端開發(fā)者,你主要知道 setTimeoutPromise 的行為與瀏覽器基本一致即可。

8. 經(jīng)典面試題集錦 + 解析

題目 1

setTimeout(() => console.log(1), 0);
Promise.resolve().then(() => console.log(2));
Promise.resolve().then(() => {
  console.log(3);
  setTimeout(() => console.log(4), 0);
});
console.log(5);

輸出5 2 3 1 4

解釋

  • 同步 5。
  • 微任務(wù)依次 23(執(zhí)行 3 時(shí)注冊(cè)了一個(gè)宏任務(wù) 4)。
  • 宏任務(wù) 1、4。

題目 2(混合 async/await)

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => console.log('promise2'));
console.log('script end');

輸出script startasync1 startasync2promise1script endasync1 endpromise2setTimeout

逐步拆解

  1. 同步:script start
  2. 調(diào)用 async1,輸出 async1 start
  3. await async2() 執(zhí)行 async2,輸出 async2,然后 await 后續(xù)代碼(async1 end)入微任務(wù)
  4. 繼續(xù)執(zhí)行同步,new Promise 立即執(zhí)行輸出 promise1resolve 后的 then 入微任務(wù)
  5. 同步 script end
  6. 當(dāng)前宏任務(wù)結(jié)束,清空微任務(wù)隊(duì)列:async1 end、promise2
  7. 下一個(gè)宏任務(wù) setTimeout 輸出

題目 3(Node.js 中的process.nextTick)

setTimeout(() => console.log('timeout'), 0);
process.nextTick(() => console.log('nextTick'));
console.log('start');

Node.js 輸出:startnextTicktimeout

process.nextTick 優(yōu)先級(jí)高于 Promise.then,屬于特殊的微任務(wù)。

9. 總結(jié)

事件循環(huán)是 JavaScript 異步執(zhí)行的核心機(jī)制。記住三句話:

  1. 先同步,后異步:同步代碼優(yōu)先執(zhí)行,異步回調(diào)在隊(duì)列中等待。
  2. 微任務(wù) > 宏任務(wù):每個(gè)宏任務(wù)執(zhí)行完后,必須清空所有微任務(wù)。
  3. await 不阻塞:它只是把后續(xù)代碼包裝成微任務(wù)。

實(shí)踐建議

  • 日常開發(fā)中,優(yōu)先用 async/await,不用手動(dòng)管理 then。
  • 需要并發(fā)請(qǐng)求時(shí)用 Promise.all。
  • 記住常見宏/微任務(wù)的分類,面試時(shí)不再慌張。

到此這篇關(guān)于深度解析JavaScript中事件循環(huán)(Event Loop)的執(zhí)行順序的文章就介紹到這了,更多相關(guān)JavaScript事件循環(huán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

鸡西市| 永仁县| 安陆市| 三河市| 右玉县| 海兴县| 徐水县| 定陶县| 新沂市| 湘乡市| 桃源县| 宝兴县| 顺昌县| 彰化县| 电白县| 大连市| 屯昌县| 六安市| 来安县| 吉林市| 桑日县| 响水县| 巧家县| 广灵县| 商城县| 班戈县| 河西区| 阳新县| 砚山县| 天镇县| 盐城市| 北海市| 潢川县| 凯里市| 庆城县| 裕民县| 巴林右旗| 佛山市| 金塔县| 康平县| 南充市|