事件循環(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ù);
-
nextTick:
process.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 Queue 和 Close Queue。
2.執(zhí)行過(guò)程
瀏覽器環(huán)境
先執(zhí)行<script>中的同步任務(wù),然后所有微任務(wù),一個(gè)宏任務(wù),所有微任務(wù),一個(gè)宏任務(wù)......
- 執(zhí)行完主執(zhí)行線程中的任務(wù);
- 取出 Microtask Queue 中任務(wù)執(zhí)行直到清空;
- 取出 Macrotask Queue 中一個(gè)任務(wù)執(zhí)行;
- 重復(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 的
then和catch才是 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 高,setTimeout和setInterval優(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