大家都知道,javascript是一門單執行緒語言,因此為了實現主執行緒的不阻塞,Event Loop這樣的方案應運而生。
瀏覽器和node中Event loop並不一樣,瀏覽器的Event loop是在HTML5中定義的規範,而node中則由libuv庫實現。
瀏覽器中的Event loop
所有同步任務都在主執行緒上執行,形成一個執行棧
主執行緒之外,還存在一個任務佇列。
任務佇列分為macro-task(巨集任務)和micro-task(微任務)。
macro-task(巨集任務): setTimeout, setInterval, setImmediate, I/O等
micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了巨集任務中),Object.observe(已廢棄), MutationObserver等
整個最基本的Event Loop如圖所示:
具體過程:
瀏覽器中,先執行當前棧,執行完主執行執行緒中的任務。
取出Microtask微任務佇列中任務執行直到清空。
取出Macrotask巨集任務中 一個 任務執行。
檢查Microtask微任務中有沒有任務,如果有任務執行直到清空。
重複3和4。
整個的這種執行機制又稱為Event Loop(事件迴圈)
例子
瞭解瀏覽器的Event loop後,檢視下面例子,猜測瀏覽器是怎麼輸出的
console.log(1);
console.log(2);
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(function(){
console.log('Promise')
})
})
setTimeout(function(){
console.log('setTimeout2');
})
//瀏覽器輸出:1 2 setTimeout1 Promise setTimeout2複製程式碼
node中的Event loop
在libuv內部有這樣一個事件環機制。在node啟動時會初始化事件環。
node中的event loop分為6個階段,不同於瀏覽器的是,這裡每一個階段都對應一個事件佇列,node會在當前階段中的全部任務執行完,清空NextTick Queue,清空Microtask Queue,再執行下一階段。
在node.js裡,process 物件代表node.js應用程式,可以獲取應用程式的使用者,執行環境等各種資訊。process.nextTick()方法將 callback 新增到
next tick 佇列
,並且nextTick優先順序比promise等microtask高。
timers:執行setTimeout() 和 setInterval()中到期的callback。
I/O callbacks:上一輪迴圈中有少數的I/Ocallback會被延遲到這一輪的這一階段執行
idle, prepare:佇列的移動,僅內部使用
poll:最為重要的階段,執行I/O callback,在適當的條件下會阻塞在這個階段
check:執行setImmediate的callback
close callbacks:執行close事件的callback,例如socket.on("close",func)
例子
檢視下面例子加深對event loop的理解
在node執行下面程式碼,發現每次執行先後順序不一樣,因為node需要啟動時間,執行過程中setTimeout可能到時間了也可能沒到時間,所以這個先後順序取決於node的執行時間。
setTimeout(function(){
console.log('timeout')
})
setImmediate(function(){
console.log('immediate')
})複製程式碼
i/o操作階段完成後,會走check階段,所以setImmediate會優先走
let fs=require('fs');
fs.readFile('./1.log',function(){
console.log('fs');
setTimeout(function(){
console.log('timeout')
})
setImmediate(funciton(){
console.log('setTimmediate')
})
})複製程式碼
nextTick應用場景
function Fn(){
this.arrs;
process.nextTick(()=>{ //根據nextTick的特性,可以先賦值,再在下一個佇列中使用
this.arrs();
})
}
Fn.prototype.then=function(){
this.arrs=function(){console.log(1)}
}
let fn=new Fn();
fn.then();
//注意:nextTick千萬不要寫遞迴,不然會造成死迴圈。可以放一些比setTimeout優先執行的任務複製程式碼
總結
同一個上下文下,MicroTask微任務會比MacroTask巨集任務先執行。
瀏覽器是先取出一個MacroTask巨集任務執行,再執行MicroTask微任務中的所有任務。Node是按照六個階段執行,每個階段切換時,再執行MicroTask微任務佇列
同個MicroTask佇列下
process.tick()
會優於Promise
setImmdieate()和setTimeout(),如果他們在非同步i/o callback之外呼叫(在i/o內呼叫因為下一階段為check階段),其執行先後順序是不確定的,需要看loop的執行前的耗時情況。