1.背景
1.1 單執行緒
我們都知道JavaScript是單執行緒的,這是它語言特性決定的,它的主要用途 在於一些I/O操作,Dom操作等,單執行緒提高了效率,但同時有很多操作比如讀取檔案,Ajax獲取資料等,都是一些比較耗時的操作,使用者等不了太長時間,因此衍生出了事件迴圈。
1.2 任務佇列
在JavaScript中,我們把佇列分為兩種,一種是同步任務,在主執行緒執行,只有一個任務執行完才可以執行下一個任務,一種是非同步任務,它不進入主執行緒,而是通過另外一種方式,叫做任務佇列
例如上圖中- 所有同步任務在Stack(棧)中執行
- 其他耗時操作在下面的Queue(佇列)中執行,佇列是先進先出的,一旦這些耗時操作執行完,就會放入佇列中
- 一旦棧中任務執行結束,系統開始讀取佇列中排隊的任務,每訪問一個佇列,會執行全部相同程式碼,比如setTimeout定時佇列,再去下一個佇列
- 整個過程不停迴圈,稱為事件迴圈
2 Node.js
2.1 事件迴圈
Node.js中也有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