與瀏覽器環境有何不同
node環境和瀏覽器環境,表現出來的事件迴圈狀態,大體表現一致 唯一不同的是:
- JS引擎存在 monitoring process 程式,會持續不斷的檢查主執行緒執行為空,一旦為空,就會去 callback queue 中檢查是否有等待被呼叫的函式。(只有巨集任務和微任務兩個佇列)
- node 中是依靠 libuv 引擎實現,我們書寫的 js 程式碼有 V8 引擎分析後去呼叫對應的 nodeAPI ,這些 api 最後由 libuv 引擎驅動,在 libuv 引擎中有一套自己的模型,把不同的事件放在不同的佇列中等待主執行緒執行。( 模型中有6種巨集任務佇列和1種微任務佇列 )
Node事件迴圈的幾個階段
// libuv引擎中的事件模型,在每個模型後面都新增了一些說明
┌───────────────────────────────────────────────────────┐
┌─>│ timers │ setTimeout/setInterval的回撥
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
│ │ pending callbacks │ 處理網路、流、tcp的錯誤回撥
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
│ │ idle, prepare │ 只在node內部使用
│ └──────────┬────────────────────────────────────────────┘
│ ↓ ┌───────────────┐
│ ┌──────────┴────────────────────────────────────────────┐ │ incoming: │
│ │ poll │ 執行poll中的i/o佇列,檢查定時器是否到時 <------│ connections,
│ └──────────┬────────────────────────────────────────────┘ │ data, etc. │
│ ↓ └───────────────┘
│ ┌──────────┴────────────────────────────────────────────┐
│ │ check │ 存放setImmediate回撥
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
└──┤ close callbacks │ 關閉的回撥(socket.on('close')...)
└───────────────────────────────────────────────────────┘
複製程式碼
Node事件迴圈中的幾個階段
官方的event-loop-timers-and-nexttick更詳細的說明
- times:這個階段執行定時器佇列中的回撥函式 (
setTimeout
和setInterval
) - pending callback:這個階段執行幾乎所有的回撥( 網路、流、tcp錯誤... )。除了,close 回撥、定時器回撥、
setImmediate
回撥這3個規定好的階段 - idle,prepare:這個階段僅在內部使用( 可以暫不理會 )
- poll:等待新的I/O事件,node在特殊情況下會阻塞這裡,檢查定時器是否到時( 入口 )
- check:
setImmediate()
的回撥會在這個階段執行 - close callbacks:例如
socket.on('close', ...)
process.nextTick
和.then()
會在事件迴圈的階段切換過程中執行
說了一堆概念,來一起看看下面這段程式碼
(function test() {
setTimeout(function () { console.log(4) }, 0);
new Promise(function (resolve, reject) {
console.log(1);
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve();
}
console.log(2);
}).then(function () {
console.log(5);
});
console.log(3);
})();
// 這段程式碼是不是很熟悉
// 最終結果1,2,3,5,4 和 瀏覽器中效果一致
複製程式碼
來點稍微高難度的
和上篇部落格 從一道執行題,瞭解瀏覽器中JS執行機制 中的程式碼一樣 (⊙﹏⊙)b
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
// 瀏覽器中的結果:1、7、8、2、4 , 5、9、11、12
// Node 中的結果:1、7、8、2、4 , 9、11、5、12
複製程式碼
解析如下:
- 在瀏覽器中
macro task
執行完成後,再次迴圈 巨集任務 的回撥佇列之前,會優先處理micro中的任務。因此結果是: 1、7、8、2、4、5、9、11、12- 在
Node
中有6個巨集任務佇列,事件迴圈首先進入 poll 階段。進入 poll 階段後檢視是否有設定的 timers ( 定時器 )時間到達,如果有一個或多個時間到達, Event Loop 將會跳過正常的迴圈流程,直接從 timers 階段執行,並執行 timers 回撥佇列,此時只有把 timers 階段的回撥佇列執行完畢後。才會走下一個階段,這也就是為什麼setTimeout
中有.then
,而沒有被立即執行的原因,當 timers 階段的回撥佇列執行完畢後,切換到下一個階段這個過程中去觸發 微任務(process.nextTick
和.then
) 。在階段與階段的切換之間。
再來一道基礎題
setTimeout(function () {
console.log('setTimeout')
});
setImmediate(function () {
console.log('setImmediate')
});
複製程式碼
執行結果:(
setTimeout、setImmediate
) 或 (setImmediate、setTimeout
)
為什麼?
setTimeout
在標準中預設的最小時間是4ms,如果開啟node和執行node程式碼的時間小於4ms,那麼程式碼解析完成後傳入libuv
引擎,首先會進入 poll 階段,此時檢視設定的時間是否達到截止時間點,如果這個時間小於4ms( 沒有達到 ),那麼會走 check 階段,會觸發setImmediate
再觸發setTimeout
。如果開啟node和執行node程式碼時間大於等於4ms,那麼就會先執行setTimeout
後執行setImmediate
在基礎上進化的經典題
setImmediate(() => {
console.log('setImmediate1')
setTimeout(() => {
console.log('setTimeout1')
}, 0);
})
setTimeout(()=>{
process.nextTick(()=>console.log('nextTick'))
console.log('setTimeout2')
setImmediate(()=>{
console.log('setImmediate2')
})
},0);
複製程式碼
兩種情況 ( nextTick執行的位置:是在佇列切換時執行 )
- 如果
setImmediate
先執行:setImmediate1、setTimeout2、setTimeout1、nextTick、setImmediate2
- 如果
setTimeout
先執行:setTimeout2、nextTick、setImmediate1、setImmediate2、setTimeout1
setImmediate和process.nextTick的直譯
- Immediate立即執行的意思,其實際上是固定在
check
階段才會被執行。這個直譯的意義和process.nextTick
才是最匹配的。- node的開發者們也清楚這兩個方法的命名上存在一定的混淆,他們表示不會把這兩個方法的名字調換過來---因為有大量的node程式使用著這兩個方法,調換命名所帶來的好處與它的影響相比不值一提。
瞭解這些東西有什麼用?
- 可以使我們對非同步程式碼的執行順序有清晰的認知( 重要的 )
- 推遲任務執行
- 面試
總結
這些概念遠比想象中的要重要
- 為什麼
new Promise
第一個引數是同步執行的 ?學習Promise && 簡易實現Promise瀏覽器
中的 JS 執行機制是什麼樣子的?從一道執行題,瞭解瀏覽器中JS執行機制
附:這篇部落格 也許 想表達 概念遠比想象中的要重要 (⊙﹏⊙)b