javaScript的事件循環(huán)(Event Loop)機(jī)制

我們首先需要理解JavaScript的事件循環(huán)(Event Loop)機(jī)制,因?yàn)樗荍avaScript異步編程的核心。事件循環(huán)允許JavaScript在執(zhí)行非阻塞I/O操作時(shí)保持高效,盡管它是單線程的。

事件循環(huán)的基本概念:

JavaScript運(yùn)行時(shí)包含一個(gè)消息隊(duì)列(或任務(wù)隊(duì)列),用于存儲(chǔ)待處理的消息(任務(wù))。每個(gè)消息都關(guān)聯(lián)著一個(gè)回調(diào)函數(shù)。事件循環(huán)會(huì)不斷地從消息隊(duì)列中取出消息并執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。

事件循環(huán)中有兩種主要的任務(wù)隊(duì)列:

  1. 宏任務(wù)(Macrotasks):包括整體script代碼、setTimeout、setInterval、I/O、UI渲染等。

  2. 微任務(wù)(Microtasks):包括Promise回調(diào)、MutationObserver、process.nextTick(Node.js)等。

事件循環(huán)的執(zhí)行順序:

  1. 執(zhí)行一個(gè)宏任務(wù)(從宏任務(wù)隊(duì)列中取出一個(gè)任務(wù)執(zhí)行)。

  2. 執(zhí)行過(guò)程中遇到微任務(wù),將其添加到微任務(wù)隊(duì)列。

  3. 宏任務(wù)執(zhí)行完畢,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)。

  4. 微任務(wù)執(zhí)行完畢,開始下一個(gè)宏任務(wù)(如果有的話)。

  5. 如此循環(huán),直到所有任務(wù)完成。

常見問(wèn)題:

1. 請(qǐng)解釋事件循環(huán)(Event Loop)是什么?

事件循環(huán)是JavaScript處理異步操作的一種機(jī)制。它通過(guò)一個(gè)循環(huán)不斷地檢查任務(wù)隊(duì)列中是否有任務(wù)需要執(zhí)行,如果有則取出執(zhí)行。它分為宏任務(wù)和微任務(wù),每次執(zhí)行一個(gè)宏任務(wù)后,會(huì)清空整個(gè)微任務(wù)隊(duì)列。

2. 宏任務(wù)(Macrotask)和微任務(wù)(Microtask)有什么區(qū)別?

  • 宏任務(wù):由宿主環(huán)境(瀏覽器、Node.js)發(fā)起的任務(wù),如setTimeout、setInterval、I/O、UI渲染、事件回調(diào)等。

  • 微任務(wù):由JavaScript引擎發(fā)起的任務(wù),如Promise回調(diào)、MutationObserver、process.nextTick(Node.js)等。

  • 執(zhí)行順序:每次事件循環(huán)中,先執(zhí)行一個(gè)宏任務(wù),然后執(zhí)行所有微任務(wù),再執(zhí)行下一個(gè)宏任務(wù),如此循環(huán)。

3. 以下代碼的輸出順序是什么?


console.log('1');

setTimeout(function() {

console.log('2');

}, 0);

Promise.resolve().then(function() {

console.log('3');

});

console.log('4');

輸出順序:1, 4, 3, 2

解釋:

  • 首先執(zhí)行同步代碼:輸出1和4。

  • 然后檢查微任務(wù)隊(duì)列,有Promise回調(diào),輸出3。

  • 最后執(zhí)行宏任務(wù)隊(duì)列中的setTimeout回調(diào),輸出2。

4. 如果嵌套宏任務(wù)和微任務(wù),執(zhí)行順序如何?


console.log('start');

setTimeout(() => {

console.log('timeout1');

Promise.resolve().then(() => {

console.log('promise1');

});

}, 0);

setTimeout(() => {

console.log('timeout2');

Promise.resolve().then(() => {

console.log('promise2');

});

}, 0);

Promise.resolve().then(() => {

console.log('promise3');

});

console.log('end');

輸出順序:start, end, promise3, timeout1, promise1, timeout2, promise2

解釋:

  • 同步代碼:輸出start和end。

  • 微任務(wù)隊(duì)列:執(zhí)行Promise回調(diào),輸出promise3。

  • 宏任務(wù)隊(duì)列:第一個(gè)setTimeout回調(diào),輸出timeout1,然后其內(nèi)部的Promise回調(diào)加入微任務(wù)隊(duì)列,執(zhí)行微任務(wù)(輸出promise1)。

  • 接著執(zhí)行第二個(gè)setTimeout回調(diào),輸出timeout2,然后其內(nèi)部的Promise回調(diào)加入微任務(wù)隊(duì)列,執(zhí)行微任務(wù)(輸出promise2)。

5. setTimeout(fn, 0) 真的在0毫秒后執(zhí)行嗎?

不一定。它表示在至少0毫秒后執(zhí)行,即盡快執(zhí)行,但實(shí)際執(zhí)行時(shí)間取決于當(dāng)前執(zhí)行棧是否為空以及消息隊(duì)列中是否有其他任務(wù)在等待。因?yàn)镴avaScript是單線程的,如果當(dāng)前有任務(wù)在執(zhí)行,那么setTimeout的回調(diào)必須等待。

6. Node.js中的事件循環(huán)和瀏覽器中的事件循環(huán)有什么區(qū)別?

在Node.js中,事件循環(huán)分為多個(gè)階段(如timers、pending callbacks、idle, prepare、poll、check、close callbacks),每個(gè)階段執(zhí)行特定的任務(wù)。而瀏覽器中的事件循環(huán)沒有這樣的階段劃分,但同樣有宏任務(wù)和微任務(wù)的概念。

在Node.js中,process.nextTick回調(diào)比微任務(wù)還要優(yōu)先執(zhí)行,即在每個(gè)階段切換時(shí)都會(huì)執(zhí)行process.nextTick隊(duì)列。

7. 什么是任務(wù)隊(duì)列(Task Queue)和作業(yè)隊(duì)列(Job Queue)?

任務(wù)隊(duì)列通常指宏任務(wù)隊(duì)列,而作業(yè)隊(duì)列通常指微任務(wù)隊(duì)列。在ECMAScript規(guī)范中,微任務(wù)隊(duì)列被稱為Job Queue。

8. 如何理解異步編程中的“饑餓”問(wèn)題?

如果微任務(wù)中不斷地產(chǎn)生新的微任務(wù),那么宏任務(wù)將永遠(yuǎn)得不到執(zhí)行,導(dǎo)致“饑餓”。因此,在編寫代碼時(shí)要注意避免微任務(wù)的無(wú)限循環(huán)。

總結(jié):

事件循環(huán)是JavaScript異步的基礎(chǔ),理解宏任務(wù)和微任務(wù)的執(zhí)行順序是解決異步代碼執(zhí)行順序問(wèn)題的關(guān)鍵。通常會(huì)通過(guò)代碼輸出題來(lái)考察對(duì)事件循環(huán)的理解。同時(shí),也要注意Node.js和瀏覽器環(huán)境在事件循環(huán)上的差異

JavaScript 事件循環(huán)(Event Loop)深度解析與高頻問(wèn)題

一、事件循環(huán)核心概念

JavaScript 是單線程語(yǔ)言,事件循環(huán)是其實(shí)現(xiàn)異步編程的核心機(jī)制:

   ┌───────────────────────┐
┌─>│       調(diào)用棧          │<───執(zhí)行上下文
│  └──────────┬────────────┘
│             │
│  ┌──────────┴────────────┐
│  │       微任務(wù)隊(duì)列       │<───Promise/MutationObserver/process.nextTick
│  └──────────┬────────────┘
│             │
│  ┌──────────┴────────────┐
│  │       宏任務(wù)隊(duì)列       │<───setTimeout/setInterval/I/O/UI渲染/事件回調(diào)
│  └──────────┬────────────┘
│             │
└─────────────┘

二、執(zhí)行順序規(guī)則

  1. 同步代碼:優(yōu)先執(zhí)行調(diào)用棧中的同步任務(wù)
  2. 微任務(wù):當(dāng)調(diào)用棧清空后,執(zhí)行所有微任務(wù)
  3. 宏任務(wù):每次從宏任務(wù)隊(duì)列取一個(gè)任務(wù)執(zhí)行
  4. 渲染:在宏任務(wù)之間執(zhí)行 UI 渲染

三、高頻題及解析

1. 基礎(chǔ)執(zhí)行順序題

console.log('1');

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

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

console.log('5');

輸出順序:1 → 5 → 4 → 2 → 3
解析

  • 同步代碼:1, 5
  • 微任務(wù):4
  • 宏任務(wù)(setTimeout):2
  • 宏任務(wù)中的微任務(wù):3

2. 混合微任務(wù)與宏任務(wù)

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

Promise.resolve().then(() => {
  console.log('B');
  setTimeout(() => console.log('C'), 0);
});

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

console.log('E');

輸出順序:E → B → D → A → C
解析

  • 同步代碼:E
  • 微任務(wù)隊(duì)列:
    • 第一個(gè) Promise:B(添加宏任務(wù)C)
    • 第二個(gè) Promise:D
  • 宏任務(wù)隊(duì)列:
    • A(先進(jìn)入隊(duì)列)
    • C(后進(jìn)入隊(duì)列)

3. async/await 執(zhí)行順序

async function async1() {
  console.log('A');
  await async2();
  console.log('B');
}

async function async2() {
  console.log('C');
}

console.log('D');

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

async1();

new Promise(resolve => {
  console.log('F');
  resolve();
}).then(() => console.log('G'));

console.log('H');

輸出順序:D → A → C → F → H → B → G → E
解析

  • await 后面的代碼相當(dāng)于放在 Promise.then
  • 等價(jià)轉(zhuǎn)換:
    // async1 轉(zhuǎn)換為:
    function async1() {
      console.log('A');
      new Promise(resolve => {
        async2();
        resolve();
      }).then(() => console.log('B'));
    }
    

4. Node.js 與瀏覽器差異

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

setImmediate(() => console.log('2'));

process.nextTick(() => console.log('3'));

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

console.log('5');

瀏覽器輸出:5 → 4 → 1 → 2 → 3(nextTick 非標(biāo)準(zhǔn))
Node.js 輸出:5 → 3 → 4 → 1 或 5 → 3 → 4 → 2 → 1
差異點(diǎn)

  1. process.nextTick 優(yōu)先級(jí)最高(Node獨(dú)有)
  2. setImmediatesetTimeout(0) 順序不確定
  3. Node 有多個(gè)階段:timers → pending → idle → poll → check → close

5. 復(fù)雜嵌套場(chǎng)景

console.log('Start');

setTimeout(() => {
  console.log('Timeout 1');
  Promise.resolve().then(() => console.log('Promise 1'));
}, 0);

setTimeout(() => {
  console.log('Timeout 2');
  Promise.resolve().then(() => {
    console.log('Promise 2');
    setTimeout(() => console.log('Timeout 3'), 0);
  });
}, 0);

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

console.log('End');

輸出順序
Start → End → Promise 3 → Timeout 1 → Promise 1 → Timeout 2 → Promise 2 → Timeout 3


四、事件循環(huán)核心知識(shí)點(diǎn)

  1. 任務(wù)隊(duì)列類型

    • 宏任務(wù):script、setTimeout、setInterval、I/O、UI渲染
    • 微任務(wù):Promise.then、MutationObserver、process.nextTick(Node)
  2. 關(guān)鍵執(zhí)行規(guī)則

    • 每執(zhí)行一個(gè)宏任務(wù)后,清空整個(gè)微任務(wù)隊(duì)列
    • UI 渲染在宏任務(wù)之間執(zhí)行
    • requestAnimationFrame 在渲染前執(zhí)行
  3. Node.js 特殊機(jī)制

    ┌───────────────────────┐
    │        timers         │<── setTimeout/setInterval
    ├───────────────────────┤
    │   pending callbacks   │<── I/O回調(diào)
    ├───────────────────────┤
    │      idle, prepare    │<── 內(nèi)部使用
    ├───────────────────────┤
    │         poll          │<── 檢索新I/O事件
    ├───────────────────────┤
    │        check          │<── setImmediate
    ├───────────────────────┤
    │     close callbacks   │<── 關(guān)閉事件回調(diào)
    └───────────────────────┘
    

五、問(wèn)題必備技巧

  1. 分析代碼時(shí)先標(biāo)記:

    • [S] 同步代碼
    • [M] 宏任務(wù)
    • [m] 微任務(wù)
  2. 解題步驟:

    1. 執(zhí)行所有同步代碼
    2. 執(zhí)行所有微任務(wù)
    3. 執(zhí)行一個(gè)宏任務(wù)
    4. 重復(fù)步驟2-3
  3. 常見陷阱:

    // 阻塞事件循環(huán)
    while (true) {} // 會(huì)阻塞所有任務(wù)
    
    // 微任務(wù)遞歸
    function recursiveMicrotask() {
      Promise.resolve().then(recursiveMicrotask);
    }
    

掌握事件循環(huán)機(jī)制是JavaScript高級(jí)開發(fā)的必備技能,建議通過(guò)Chrome DevTools的Performance面板實(shí)時(shí)觀察調(diào)用棧執(zhí)行過(guò)程加深理解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容