從一道執行題,瞭解Node中JS執行機制

與瀏覽器環境有何不一樣

node環境和瀏覽器環境,表現出來的事件循環狀態,大致表現一致 惟一不一樣的是:javascript

  1. JS引擎存在 monitoring process 進程,會持續不斷的檢查主線程執行爲空,一旦爲空,就會去 callback queue 中檢查是否有等待被調用的函數。(只有宏任務和微任務兩個隊列)
  2. 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更詳細的說明java

  1. times:這個階段執行定時器隊列中的回調函數 ( setTimeoutsetInterval )
  2. pending callback:這個階段執行幾乎全部的回調( 網絡、流、tcp錯誤... )。除了,close 回調、定時器回調、setImmediate 回調這3個規定好的階段
  3. idle,prepare:這個階段僅在內部使用( 能夠暫不理會 )
  4. poll:等待新的I/O事件,node在特殊狀況下會阻塞這裏,檢查定時器是否到時( 入口 )
  5. checksetImmediate() 的回調會在這個階段執行
  6. close callbacks:例如 socket.on('close', ...)
  7. 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執行機制 中的代碼同樣 (⊙﹏⊙)bnode

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)
  })
})
// 瀏覽器中的結果:一、七、八、二、4 , 五、九、十一、12
// Node 中的結果:一、七、八、二、4 , 九、十一、五、12
複製代碼

解析以下:面試

  1. 在瀏覽器中 macro task 執行完成後,再次循環 宏任務 的回調隊列以前,會優先處理micro中的任務。所以結果是: 一、七、八、二、四、五、九、十一、12
  2. 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 )api

爲何? 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執行的位置:是在隊列切換時執行 )網絡

  1. 若是 setImmediate 先執行:setImmediate一、setTimeout二、setTimeout一、nextTick、setImmediate2
  2. 若是 setTimeout 先執行:setTimeout二、nextTick、setImmediate一、setImmediate二、setTimeout1

setImmediate和process.nextTick的直譯

  1. Immediate當即執行的意思,其其實是固定在 check 階段纔會被執行。這個直譯的意義和 process.nextTick 纔是最匹配的。
  2. node的開發者們也清楚這兩個方法的命名上存在必定的混淆,他們表示不會把這兩個方法的名字調換過來---由於有大量的node程序使用着這兩個方法,調換命名所帶來的好處與它的影響相比不值一提。

瞭解這些東西有什麼用?

  1. 可使咱們對異步代碼的執行順序有清晰的認知( 重要的 )
  2. 推遲任務執行
  3. 面試

總結

這些概念遠比想象中的要重要異步

  1. 爲何 new Promise 第一個參數是同步執行的 ?學習Promise && 簡易實現Promise
  2. 瀏覽器 中的 JS 執行機制是什麼樣子的?從一道執行題,瞭解瀏覽器中JS執行機制

附:這篇博客 也許 想表達 概念遠比想象中的要重要 (⊙﹏⊙)bsocket

相關文章
相關標籤/搜索