轉(zhuǎn)載自https://segmentfault.com/a/1190000008595101
『前端碎碎念』系列會(huì)記錄我平時(shí)看書或者看文章遇到的問題,一般都是比較基礎(chǔ)但是容易遺忘的知識(shí)點(diǎn),你也可能會(huì)在面試中碰到。 我會(huì)查閱一些資料并可能加上自己的理解,來記錄這些問題。更多文章請(qǐng)前往我的個(gè)人博客
這個(gè)問題是有關(guān)執(zhí)行順序和Event Loop的。關(guān)于Event Loop和任務(wù)隊(duì)列等概念,可以先閱讀我引用中的文章,本文主要分析一些存在的疑惑點(diǎn)。
下面這個(gè)例子比較典型:
setImmediate(function(){console.log(1);},0);setTimeout(function(){console.log(2);},0);newPromise(function(resolve){console.log(3);? ? resolve();console.log(4);}).then(function(){console.log(5);});console.log(6);process.nextTick(function(){console.log(7);});console.log(8);//輸出結(jié)果是3 4 6 8 7 5 2 1
在解釋輸出結(jié)果之前,我們來看幾個(gè)概念:
macro-task: script (整體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering.
micro-task: process.nextTick, Promise(原生),Object.observe,MutationObserver
除了script整體代碼,micro-task的任務(wù)優(yōu)先級(jí)高于macro-task的任務(wù)優(yōu)先級(jí)。其中,script(整體代碼) ,可以理解為待執(zhí)行的所有代碼。
所以執(zhí)行順序如下:
第一步. script整體代碼被執(zhí)行,執(zhí)行過程為
創(chuàng)建setImmediate macro-task
創(chuàng)建setTimeout macro-task
創(chuàng)建micro-task Promise.then 的回調(diào),并執(zhí)行script console.log(3); resolve(); console.log(4); 此時(shí)輸出3和4,雖然resolve調(diào)用了,執(zhí)行了但是整體代碼還沒執(zhí)行完,無(wú)法進(jìn)入Promise.then 流程。
console.log(6)輸出6
process.nextTick 創(chuàng)建micro-task
console.log(8) 輸出8
第一個(gè)過程過后,已經(jīng)輸出了3 4 6 8
第二步. 由于其他micro-task 的 優(yōu)先級(jí)高于macro-task。
此時(shí)micro-task 中有兩個(gè)任務(wù)按照優(yōu)先級(jí)process.nextTick 高于 Promise。
所以先輸出7,再輸出5
第三步,micro-task 任務(wù)列表已經(jīng)執(zhí)行完畢,家下來執(zhí)行macro-task. 由于setTimeout的優(yōu)先級(jí)高于setIImmediate,所以先輸出2,再輸出1。
整個(gè)過程描述起來像是同步操作,實(shí)際上是基于Event Loop的事件循環(huán)。
關(guān)于micro-task和macro-task的執(zhí)行順序,可看下面這個(gè)例子(來自《深入淺出Node.js》):
//加入兩個(gè)nextTick的回調(diào)函數(shù)process.nextTick(function(){console.log('nextTick延遲執(zhí)行1');});process.nextTick(function(){console.log('nextTick延遲執(zhí)行2');});// 加入兩個(gè)setImmediate()的回調(diào)函數(shù)setImmediate(function(){console.log('setImmediate延遲執(zhí)行1');// 進(jìn)入下次循環(huán)process.nextTick(function(){console.log('強(qiáng)勢(shì)插入');? ? });});setImmediate(function(){console.log('setImmediate延遲執(zhí)行2'); });console.log('正常執(zhí)行');
書中給出的執(zhí)行結(jié)果是:
正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1強(qiáng)勢(shì)插入setImmediate延遲執(zhí)行2
process.nextTick在兩個(gè)setImmediate之間強(qiáng)行插入了。但運(yùn)行這段代碼發(fā)現(xiàn)結(jié)果卻是這樣:
正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1setImmediate延遲執(zhí)行2強(qiáng)勢(shì)插入
樸老師寫那本書的時(shí)候,node最新版本為0.10.13,而我的版本是6.x
老版本的Node會(huì)優(yōu)先執(zhí)行process.nextTick。當(dāng)process.nextTick隊(duì)列執(zhí)行完后再執(zhí)行一個(gè)setImmediate任務(wù)。然后再次回到新的事件循環(huán)。所以執(zhí)行完第一個(gè)setImmediate后,隊(duì)列里只剩下第一個(gè)setImmediate里的process.nextTick和第二個(gè)setImmediate。所以process.nextTick會(huì)先執(zhí)行。
而在新版的Node中,process.nextTick執(zhí)行完后,會(huì)循環(huán)遍歷setImmediate,將setImmediate都執(zhí)行完畢后再跳出循環(huán)。所以兩個(gè)setImmediate執(zhí)行完后隊(duì)列里只剩下第一個(gè)setImmediate里的process.nextTick。最后輸出"強(qiáng)勢(shì)插入"。
具體實(shí)現(xiàn)可參考Node.js源碼。
關(guān)于優(yōu)先級(jí)的另一個(gè)比較清晰的版本:
觀察者優(yōu)先級(jí)
在每次輪訓(xùn)檢查中,各觀察者的優(yōu)先級(jí)分別是:
idle觀察者 > I/O觀察者 > check觀察者。
idle觀察者:process.nextTick
I/O觀察者:一般性的I/O回調(diào),如網(wǎng)絡(luò),文件,數(shù)據(jù)庫(kù)I/O等
check觀察者:setImmediate,setTimeout
setImmediate 和 setTimeout 的優(yōu)先級(jí)
看下面這個(gè)例子:
setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },0);console.log('3');//輸出結(jié)果是3 2 1
我們知道現(xiàn)在HTML5規(guī)定setTimeout的最小間隔時(shí)間是4ms,也就是說0實(shí)際上也會(huì)別默認(rèn)設(shè)置為最小值4ms。我們把這個(gè)延遲加大
上面說到setTimeout 的優(yōu)先級(jí)比 setImmediate的高,其實(shí)這種說法是有條件的。
再看下面這個(gè)例子,為setTimeout增加了一個(gè)延遲20ms的時(shí)間:
setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },20);console.log('3');//輸出結(jié)果是3 2 1
setTimeout延遲20ms再執(zhí)行,而setImmediate是立即執(zhí)行,竟然2比1還先輸出??
試試打印出這個(gè)程序的執(zhí)行時(shí)間:
vart1 = +newDate();setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },20);console.log('3');vart2 = +newDate();console.log('time: '+ (t2 - t1));//輸出3time:2321
程序執(zhí)行用了23ms, 也就是說,在script(整體代碼)執(zhí)行完之前,setTimeout已經(jīng)過時(shí)了,所以當(dāng)進(jìn)入macro-task的時(shí)候setTimeout依然優(yōu)先于setImmediate執(zhí)行。如果我們把這個(gè)值調(diào)大一點(diǎn)呢?
vart1 = +newDate();setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },30);console.log('3');vart2 = +newDate();console.log('time: '+ (t2 - t1));//輸出3time:2312
setImmediate早于setTimeout執(zhí)行了,因?yàn)檫M(jìn)入macro-task 循環(huán)的時(shí)候,setTimeout的定時(shí)器還沒到。
以上實(shí)驗(yàn)是基于6.6.0版本Node.js測(cè)試,實(shí)際上在碰到類似這種問題的時(shí)候,最好的辦法是參考標(biāo)準(zhǔn),并查閱源碼,不能死記概念和順序,因?yàn)闃?biāo)準(zhǔn)也是會(huì)變的。包括此文也是自學(xué)總結(jié),經(jīng)供參考。
參考:
https://www.zhihu.com/questio...
https://segmentfault.com/a/11...