轉(zhuǎn)載自https://segmentfault.com/a/1190000008595101
『前端碎碎念』系列會記錄我平時看書或者看文章遇到的問題青瀑,一般都是比較基礎但是容易遺忘的知識點瀑粥,你也可能會在面試中碰到埋酬。 我會查閱一些資料并可能加上自己的理解,來記錄這些問題柱告。更多文章請前往我的個人博客
這個問題是有關執(zhí)行順序和Event Loop的掠廓。關于Event Loop和任務隊列等概念亏狰,可以先閱讀我引用中的文章,本文主要分析一些存在的疑惑點每瞒。
下面這個例子比較典型:
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é)果之前金闽,我們來看幾個概念:
macro-task: script (整體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering.
micro-task: process.nextTick, Promise(原生)剿骨,Object.observe代芜,MutationObserver
除了script整體代碼,micro-task的任務優(yōu)先級高于macro-task的任務優(yōu)先級浓利。其中挤庇,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); 此時輸出3和4,雖然resolve調(diào)用了淘菩,執(zhí)行了但是整體代碼還沒執(zhí)行完遵班,無法進入Promise.then 流程。
console.log(6)輸出6
process.nextTick 創(chuàng)建micro-task
console.log(8) 輸出8
第一個過程過后潮改,已經(jīng)輸出了3 4 6 8
第二步. 由于其他micro-task 的 優(yōu)先級高于macro-task狭郑。
此時micro-task 中有兩個任務按照優(yōu)先級process.nextTick 高于 Promise。
所以先輸出7汇在,再輸出5
第三步翰萨,micro-task 任務列表已經(jīng)執(zhí)行完畢,家下來執(zhí)行macro-task. 由于setTimeout的優(yōu)先級高于setIImmediate糕殉,所以先輸出2亩鬼,再輸出1。
整個過程描述起來像是同步操作阿蝶,實際上是基于Event Loop的事件循環(huán)雳锋。
關于micro-task和macro-task的執(zhí)行順序,可看下面這個例子(來自《深入淺出Node.js》):
//加入兩個nextTick的回調(diào)函數(shù)process.nextTick(function(){console.log('nextTick延遲執(zhí)行1');});process.nextTick(function(){console.log('nextTick延遲執(zhí)行2');});// 加入兩個setImmediate()的回調(diào)函數(shù)setImmediate(function(){console.log('setImmediate延遲執(zhí)行1');// 進入下次循環(huán)process.nextTick(function(){console.log('強勢插入');? ? });});setImmediate(function(){console.log('setImmediate延遲執(zhí)行2'); });console.log('正常執(zhí)行');
書中給出的執(zhí)行結(jié)果是:
正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1強勢插入setImmediate延遲執(zhí)行2
process.nextTick在兩個setImmediate之間強行插入了羡洁。但運行這段代碼發(fā)現(xiàn)結(jié)果卻是這樣:
正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1setImmediate延遲執(zhí)行2強勢插入
樸老師寫那本書的時候玷过,node最新版本為0.10.13,而我的版本是6.x
老版本的Node會優(yōu)先執(zhí)行process.nextTick筑煮。當process.nextTick隊列執(zhí)行完后再執(zhí)行一個setImmediate任務辛蚊。然后再次回到新的事件循環(huán)。所以執(zhí)行完第一個setImmediate后真仲,隊列里只剩下第一個setImmediate里的process.nextTick和第二個setImmediate袋马。所以process.nextTick會先執(zhí)行。
而在新版的Node中秸应,process.nextTick執(zhí)行完后虑凛,會循環(huán)遍歷setImmediate,將setImmediate都執(zhí)行完畢后再跳出循環(huán)灸眼。所以兩個setImmediate執(zhí)行完后隊列里只剩下第一個setImmediate里的process.nextTick卧檐。最后輸出"強勢插入"。
具體實現(xiàn)可參考Node.js源碼焰宣。
關于優(yōu)先級的另一個比較清晰的版本:
觀察者優(yōu)先級
在每次輪訓檢查中霉囚,各觀察者的優(yōu)先級分別是:
idle觀察者 > I/O觀察者 > check觀察者。
idle觀察者:process.nextTick
I/O觀察者:一般性的I/O回調(diào)匕积,如網(wǎng)絡盈罐,文件榜跌,數(shù)據(jù)庫I/O等
check觀察者:setImmediate,setTimeout
setImmediate 和 setTimeout 的優(yōu)先級
看下面這個例子:
setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },0);console.log('3');//輸出結(jié)果是3 2 1
我們知道現(xiàn)在HTML5規(guī)定setTimeout的最小間隔時間是4ms盅粪,也就是說0實際上也會別默認設置為最小值4ms钓葫。我們把這個延遲加大
上面說到setTimeout 的優(yōu)先級比 setImmediate的高,其實這種說法是有條件的票顾。
再看下面這個例子础浮,為setTimeout增加了一個延遲20ms的時間:
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還先輸出豆同??
試試打印出這個程序的執(zhí)行時間:
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)過時了,所以當進入macro-task的時候setTimeout依然優(yōu)先于setImmediate執(zhí)行蝉绷。如果我們把這個值調(diào)大一點呢鸭廷?
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í)行了,因為進入macro-task 循環(huán)的時候熔吗,setTimeout的定時器還沒到辆床。
以上實驗是基于6.6.0版本Node.js測試,實際上在碰到類似這種問題的時候磁滚,最好的辦法是參考標準佛吓,并查閱源碼宵晚,不能死記概念和順序垂攘,因為標準也是會變的。包括此文也是自學總結(jié)淤刃,經(jīng)供參考晒他。
參考:
https://www.zhihu.com/questio...
https://segmentfault.com/a/11...