『前端碎碎念』系列會記錄我平時看書或者看文章遇到的問題,通常都是比較基礎可是容易遺忘的知識點,你也可能會在面試中碰到。 我會查閱一些資料並可能加上本身的理解,來記錄這些問題。更多文章請前往個人我的博客javascript
這個問題是有關執行順序和Event Loop的。關於Event Loop和任務隊列等概念,能夠先閱讀我引用中的文章,本文主要分析一些存在的疑惑點。前端
下面這個例子比較典型:java
setImmediate(function(){ console.log(1); },0); setTimeout(function(){ console.log(2); },0); new Promise(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); //輸出結果是3 4 6 8 7 5 2 1
在解釋輸出結果以前,咱們來看幾個概念:node
macro-task: script (總體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering.
micro-task: process.nextTick, Promise(原生),Object.observe,MutationObservergit
除了script總體代碼,micro-task的任務優先級高於macro-task的任務優先級。
其中,script(總體代碼) ,能夠理解爲待執行的全部代碼。github
因此執行順序以下:面試
第一步. script總體代碼被執行,執行過程爲數據庫
建立setImmediate macro-tasksegmentfault
建立setTimeout macro-task網絡
建立micro-task Promise.then 的回調,並執行script console.log(3); resolve(); console.log(4); 此時輸出3和4,雖然resolve調用了,執行了可是總體代碼還沒執行完,沒法進入Promise.then 流程。
console.log(6)輸出6
process.nextTick 建立micro-task
console.log(8) 輸出8
第一個過程事後,已經輸出了3 4 6 8
第二步. 因爲其餘micro-task 的 優先級高於macro-task。
此時micro-task 中有兩個任務按照優先級process.nextTick 高於 Promise。
因此先輸出7,再輸出5
第三步,micro-task 任務列表已經執行完畢,家下來執行macro-task. 因爲setTimeout的優先級高於setIImmediate,因此先輸出2,再輸出1。
整個過程描述起來像是同步操做,其實是基於Event Loop的事件循環。
關於micro-task和macro-task的執行順序,可看下面這個例子(來自《深刻淺出Node.js》):
//加入兩個nextTick的回調函數 process.nextTick(function () { console.log('nextTick延遲執行1'); }); process.nextTick(function () { console.log('nextTick延遲執行2'); }); // 加入兩個setImmediate()的回調函數 setImmediate(function () { console.log('setImmediate延遲執行1'); // 進入下次循環 process.nextTick(function () { console.log('強勢插入'); }); }); setImmediate(function () { console.log('setImmediate延遲執行2'); }); console.log('正常執行');
書中給出的執行結果是:
正常執行 nextTick延遲執行1 nextTick延遲執行2 setImmediate延遲執行1 強勢插入 setImmediate延遲執行2
process.nextTick在兩個setImmediate之間強行插入了。
但運行這段代碼發現結果倒是這樣:
正常執行 nextTick延遲執行1 nextTick延遲執行2 setImmediate延遲執行1 setImmediate延遲執行2 強勢插入
樸老師寫那本書的時候,node最新版本爲0.10.13,而個人版本是6.x
老版本的Node會優先執行process.nextTick。
當process.nextTick隊列執行完後再執行一個setImmediate任務。而後再次回到新的事件循環。因此執行完第一個setImmediate後,隊列裏只剩下第一個setImmediate裏的process.nextTick和第二個setImmediate。因此process.nextTick會先執行。
而在新版的Node中,process.nextTick執行完後,會循環遍歷setImmediate,將setImmediate都執行完畢後再跳出循環。因此兩個setImmediate執行完後隊列裏只剩下第一個setImmediate裏的process.nextTick。最後輸出"強勢插入"。
具體實現可參考Node.js源碼。
關於優先級的另外一個比較清晰的版本:
觀察者優先級
在每次輪訓檢查中,各觀察者的優先級分別是:
idle觀察者 > I/O觀察者 > check觀察者。
idle觀察者:process.nextTick
I/O觀察者:通常性的I/O回調,如網絡,文件,數據庫I/O等
check觀察者:setImmediate,setTimeout
setImmediate 和 setTimeout 的優先級
看下面這個例子:
setImmediate(function () { console.log('1'); }); setTimeout(function () { console.log('2'); }, 0); console.log('3'); //輸出結果是3 2 1
咱們知道如今HTML5規定setTimeout的最小間隔時間是4ms,也就是說0實際上也會別默認設置爲最小值4ms。咱們把這個延遲加大
上面說到setTimeout 的優先級比 setImmediate的高,其實這種說法是有條件的。
再看下面這個例子,爲setTimeout增長了一個延遲20ms的時間:
setImmediate(function () { console.log('1'); }); setTimeout(function () { console.log('2'); }, 20); console.log('3'); //輸出結果是3 2 1
setTimeout延遲20ms再執行,而setImmediate是當即執行,居然2比1還先輸出??
試試打印出這個程序的執行時間:
var t1 = +new Date(); setImmediate(function () { console.log('1'); }); setTimeout(function () { console.log('2'); },20); console.log('3'); var t2 = +new Date(); console.log('time: ' + (t2 - t1)); //輸出 3 time: 23 2 1
程序執行用了23ms, 也就是說,在script(總體代碼)執行完以前,setTimeout已通過時了,因此當進入macro-task的時候setTimeout依然優先於setImmediate執行。若是咱們把這個值調大一點呢?
var t1 = +new Date(); setImmediate(function () { console.log('1'); }); setTimeout(function () { console.log('2'); },30); console.log('3'); var t2 = +new Date(); console.log('time: ' + (t2 - t1)); //輸出 3 time: 23 1 2
setImmediate早於setTimeout執行了,由於進入macro-task 循環的時候,setTimeout的定時器還沒到。
以上實驗是基於6.6.0版本Node.js測試,實際上在碰到相似這種問題的時候,最好的辦法是參考標準,並查閱源碼,不能死記概念和順序,由於標準也是會變的。包括此文也是自學總結,經供參考。
參考:
https://www.zhihu.com/questio...
https://segmentfault.com/a/11...
http://www.jianshu.com/p/837b...