Event Loop事件迴圈,看完你總會有點收穫!

Jeffywin發表於2018-07-31

1.背景

1.1 單執行緒

我們都知道JavaScript是單執行緒的,這是它語言特性決定的,它的主要用途 在於一些I/O操作,Dom操作等,單執行緒提高了效率,但同時有很多操作比如讀取檔案,Ajax獲取資料等,都是一些比較耗時的操作,使用者等不了太長時間,因此衍生出了事件迴圈。

1.2 任務佇列

在JavaScript中,我們把佇列分為兩種,一種是同步任務,在主執行緒執行,只有一個任務執行完才可以執行下一個任務,一種是非同步任務,它不進入主執行緒,而是通過另外一種方式,叫做任務佇列

Event Loop事件迴圈,看完你總會有點收穫!
例如上圖中

  • 所有同步任務在Stack(棧)中執行
  • 其他耗時操作在下面的Queue(佇列)中執行,佇列是先進先出的,一旦這些耗時操作執行完,就會放入佇列中
  • 一旦棧中任務執行結束,系統開始讀取佇列中排隊的任務,每訪問一個佇列,會執行全部相同程式碼,比如setTimeout定時佇列,再去下一個佇列
  • 整個過程不停迴圈,稱為事件迴圈

2 Node.js

2.1 事件迴圈

Node.js中也有Event Loop,不過它的機制和和瀏覽器中的略微不同

Event Loop事件迴圈,看完你總會有點收穫!
如上圖是經典的Node.js中事件迴圈圖,Node.js採用谷歌V8作為解析引擎,在I/O處理方面使用了libuv庫,它是一個基於事件驅動的跨平臺抽象層,封裝了不同作業系統一些底層特性,對外提供統一的API,上圖的事件迴圈機制是它裡面的實現。 每次事件迴圈都包含了六個階段:

  • timers階段:包括setTimeout,setInterval等
  • pedding callbacks: 執行一些系統呼叫錯誤
  • idle,prepare: node內部使用
  • poll: 輪詢階段,檢查I/O佇列中定時器是否到時,例如(讀取檔案)
  • check:執行setImmediate() 設定的callbacks
  • close callbacks: 關閉回撥

Event loop的每一次迴圈都需要依次經過上述的階段。每個階段都有自己的callback佇列,每當進入某個階段,都會從所屬的佇列中取出callback來執行,當佇列為空或者被執行callback的數量達到系統的最大數量時,進入下一階段。這六個階段都執行完畢稱為一輪迴圈。

其中還有一些微任務和巨集任務的概念 微任務:promise.then process.nextTick(),其中nextTick()執行會比前者快 巨集任務:setTimeout setImmidate(ie) messageChannel等

微任務總是會比巨集任務先執行

2.2例子

setTimeout(() => {
    console.log('timeout1');
    process.nextTick(()=>{
        console.log('nextTick1');
    })
}, 1000);
setTimeout(()=>{
    console.log('timeout2')
},1000)
複製程式碼

結果:timeout1 => timeout2 =>nextTick1 原因:首先都是外面巨集任務,先執行時間佇列,從上到下依次執行完timeout1=>timeout2,執行完後,到下一個佇列,執行nextTick()

let fs = require('fs');
fs.readFile('./index.html',function(){ 
    setImmediate(function(){
        console.log('setImmediate')
    });
    setTimeout(function(){
        console.log('setTimeout')
    },0); 
})
複製程式碼

結果:setImmediate => setTimeout 原因:讀取檔案是I/O操作,是在poll輪詢階段,讀取完後,下一階段是check,這時看有沒有setImmediate,如果有,先執行這個,再執行setTimeout

let fs = require('fs');
setTimeout(function(){
    Promise.resolve().then(()=>{
        console.log('then2');
    })
},0);
Promise.resolve().then(()=>{
    console.log('then1');
});
fs.readFile('./index.html',function(){
    process.nextTick(function(){
        console.log('nextTick')
    })
    setImmediate(()=>{
        console.log('setImmediate')
    });
});

複製程式碼

結果:then1 => then2=> nextTick => setImmediate 原因:首先看最外面有微任務Promise.then,所以列印then1,接著到時間佇列times,執行then2,times => poll 是不同階段,每執行不同階段, process.nextTick 不屬於事件迴圈的任何一個階段,它屬於該階段與下階段之間的過渡, 即本階段執行結束, 進入下一個階段前, 所要執行的回撥。所以 都要檢查當前有沒有promise,或者nextTick,如果有,先執行這些,有nextTick,再執行setImmediate

相關文章