你們都知道,javascript是一門單線程語言,所以爲了實現主線程的不阻塞,Event Loop這樣的方案應運而生。javascript
瀏覽器和node中Event loop並不同,瀏覽器的Event loop是在HTML5中定義的規範,而node中則由libuv庫實現。java
全部同步任務都在主線程上執行,造成一個執行棧node
主線程以外,還存在一個任務隊列。promise
任務隊列分爲macro-task(宏任務)和micro-task(微任務)。瀏覽器
macro-task(宏任務): setTimeout, setInterval, setImmediate, I/O等bash
micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了宏任務中),Object.observe(已廢棄), MutationObserver等異步
整個最基本的Event Loop如圖所示:socket
具體過程:oop
瀏覽器中,先執行當前棧,執行完主執行線程中的任務。ui
取出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複製代碼
在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的執行前的耗時狀況。