瀏覽器和Node事件循環(huán)的區(qū)別

事件循環(huán),是 js 中老生常談的一個(gè)話題了,而在瀏覽器和 Node 中的事件循環(huán)執(zhí)行機(jī)制也不相同,瀏覽器的事件循環(huán)是在 HTML5 中定義的規(guī)范,而 Node 中則是由 libuv 庫(kù)實(shí)現(xiàn),不可以混為一談。

先看一個(gè)簡(jiǎn)單的事件循環(huán)筆試題:

function sleep(time) {
    let startTime = new Date();
    while (new Date() - startTime < time) {}
    console.log('<--Next Loop-->');
}

setTimeout(() => {
    console.log('timeout1');
    setTimeout(() => {
        console.log('timeout3');
        sleep(1000);
    });
    new Promise((resolve) => {
        console.log('timeout1_promise');
        resolve();
    }).then(() => {
        console.log('timeout1_then');
    });
    sleep(1000);
});
     
setTimeout(() => {
    console.log('timeout2');
    setTimeout(() => {
        console.log('timeout4');
        sleep(1000);
    });
    new Promise((resolve) => {
        console.log('timeout2_promise');
        resolve();
    }).then(() => {
        console.log('timeout2_then');
    });
    sleep(1000);
});

在不同的環(huán)境中,輸出的結(jié)果也是不同的:

  • 瀏覽器中的輸出:
timeout1
timeout1_promise
<--Next Loop-->
timeout1_then
timeout2
timeout2_promise
<--Next Loop-->
timeout2_then
timeout3
<--Next Loop-->
timeout4
<--Next Loop-->
  • Node 環(huán)境中的輸出:
timeout1
timeout1_promise
<--Next Loop-->
timeout2
timeout2_promise
<--Next Loop-->
timeout1_then
timeout2_then
timeout3
<--Next Loop-->
timeout4
<--Next Loop-->

接下來(lái)我們就看看瀏覽器和 Node 中時(shí)間循環(huán)的區(qū)別是什么。

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

瀏覽器環(huán)境

瀏覽器環(huán)境下的 異步任務(wù) 分為 宏任務(wù)(macroTask)微任務(wù)(microTask)

  • 宏任務(wù)(macroTask):script 中代碼、setTimeout、setInterval、I/O、UI render;
  • 微任務(wù)(microTask): Promise、Object.observe、MutationObserver。

當(dāng)滿足執(zhí)行條件時(shí),宏任務(wù)(macroTask)微任務(wù)(microTask) 會(huì)各自被放入對(duì)應(yīng)的隊(duì)列:宏隊(duì)列(Macrotask Queue)微隊(duì)列(Microtask Queue) 中等待執(zhí)行。

Node 環(huán)境

在 Node 環(huán)境中 任務(wù)類型 相對(duì)就比瀏覽器環(huán)境下要復(fù)雜一些:

  • microTask:微任務(wù);
  • nextTickprocess.nextTick;
  • timers:執(zhí)行滿足條件的 setTimeout 、setInterval 回調(diào);
  • I/O callbacks:是否有已完成的 I/O 操作的回調(diào)函數(shù),來(lái)自上一輪的 poll 殘留;
  • poll:等待還沒(méi)完成的 I/O 事件,會(huì)因 timers 和超時(shí)時(shí)間等結(jié)束等待;
  • check:執(zhí)行 setImmediate 的回調(diào);
  • close callbacks:關(guān)閉所有的 closing handles ,一些 onclose 事件;
  • idle/prepare 等等:可忽略。

因此,也就產(chǎn)生了執(zhí)行事件循環(huán)相應(yīng)的任務(wù)隊(duì)列 Timers Queue、I/O Queue、Check QueueClose Queue。

2.執(zhí)行過(guò)程

瀏覽器環(huán)境

先執(zhí)行<script>中的同步任務(wù),然后所有微任務(wù),一個(gè)宏任務(wù),所有微任務(wù),一個(gè)宏任務(wù)......

    1. 執(zhí)行完主執(zhí)行線程中的任務(wù);
    1. 取出 Microtask Queue 中任務(wù)執(zhí)行直到清空;
    1. 取出 Macrotask Queue 中一個(gè)任務(wù)執(zhí)行;
    1. 重復(fù) 2 和 3 。

需要 注意 的是:

  • 在瀏覽器頁(yè)面中可以認(rèn)為初始執(zhí)行線程中沒(méi)有代碼,每一個(gè)<script>中的代碼是一個(gè)獨(dú)立的 task ,即會(huì)執(zhí)行完前面的<script>中創(chuàng)建的 microTask 再執(zhí)行后面的<script>中的同步代碼;
  • 如果 microTask 一直被添加,則會(huì)繼續(xù)執(zhí)行 microTask ,“卡死” macroTask;
  • 部分版本瀏覽器有執(zhí)行順序與上述不符的情況,可能是不符合標(biāo)準(zhǔn)或 js 與 html 部分標(biāo)準(zhǔn)沖突;
  • Promise 的thencatch才是 microTask ,本身的內(nèi)部代碼不是;
  • 個(gè)別瀏覽器獨(dú)有API未列出。

Node 環(huán)境

循環(huán)之前

在進(jìn)入第一次循環(huán)之前,會(huì)先進(jìn)行如下操作:

  • 同步任務(wù);
  • 發(fā)出異步請(qǐng)求;
  • 規(guī)劃定時(shí)器生效的時(shí)間;
  • 執(zhí)行process.nextTick()。

開(kāi)始循環(huán)

循環(huán)中進(jìn)行的操作:

  • 清空當(dāng)前循環(huán)內(nèi)的 Timers Queue,清空 NextTick Queue,清空 Microtask Queue;
  • 清空當(dāng)前循環(huán)內(nèi)的 I/O Queue,清空 NextTick Queue,清空 Microtask Queue
  • 清空當(dāng)前循環(huán)內(nèi)的 Check Queue,清空 NextTick Queue,清空 Microtask Queue;
  • 清空當(dāng)前循環(huán)內(nèi)的 Close Queue,清空 NextTick Queue,清空 Microtask Queue;
  • 進(jìn)入下輪循環(huán)。

可以看出,nextTick 優(yōu)先級(jí)比 Promise 等 microTask 高,setTimeoutsetInterval優(yōu)先級(jí)比setImmediate高。

注意

在整個(gè)過(guò)程中,需要 注意 的是:

  • 如果在 timers 階段執(zhí)行時(shí)創(chuàng)建了setImmediate 則會(huì)在此輪循環(huán)的 check 階段執(zhí)行,如果在 timers 階段創(chuàng)建了setTimeout,由于 timers 已取出完畢,則會(huì)進(jìn)入下輪循環(huán),check 階段創(chuàng)建 timers 任務(wù)同理;
  • setTimeout優(yōu)先級(jí)比setImmediate高,但是由于setTimeout(fn,0)的真正延遲不可能完全為 0 秒,可能出現(xiàn)先創(chuàng)建的setTimeout(fn,0)而比setImmediate的回調(diào)后執(zhí)行的情況。

總結(jié)

事件循環(huán)在 瀏覽器Node 中的區(qū)別很容易被人忽視,執(zhí)行順序整理如下:

瀏覽器環(huán)境下:

while (true) {
    宏任務(wù)隊(duì)列.shift();
    微任務(wù)隊(duì)列全部任務(wù)();
}

Node 環(huán)境下:

while (true) {
    loop.forEach((階段) => {
        階段全部任務(wù)();
        nextTick全部任務(wù)();
        microTask全部任務(wù)();
    });
    loop = loop.next;
}

個(gè)人覺(jué)得比較清晰了,有什么問(wèn)題可以私信討論。

文章部分理論參考自:https://segmentfault.com/a/1190000013660033?utm_source=channel-hottest

最后編輯于
?著作權(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)容