三個關(guān)鍵字:
EventLoop: 事件循環(huán)
MicroTask: 微任務(wù)
MacroTask: 宏任務(wù)
三個要點:
- js是單線程語言
- 任務(wù)隊列
- EventLoop 是js的執(zhí)行機制
一 js為什么是單線程語言?
js是單線程語言與它的用途相關(guān),js是瀏覽器腳本語言,主要用于與用戶的交互以及操作dom節(jié)點,所以要避免復(fù)雜的同步問題,這就決定了它必須是單線程的。比如:假如js是多線程的,一個線程在dom節(jié)點上添加內(nèi)容,一個線程刪除該dom節(jié)點,這時瀏覽器該怎么執(zhí)行?
二 任務(wù)隊列
2.1 同步任務(wù)和異步任務(wù)
由于js是單線程的,所以就意味著任務(wù)需要排隊執(zhí)行,任務(wù)又分為同步任務(wù)和異步任務(wù)。
同步任務(wù):在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完才會執(zhí)行下一個任務(wù),如果前一個任務(wù)耗時長,后一個任務(wù)就會一直等待,容易造成阻塞。
異步任務(wù):最開始不進入主線程,而是進入任務(wù)隊列中的任務(wù),當(dāng)主線程執(zhí)行完畢,系統(tǒng)會去查看任務(wù)隊列,然后決定執(zhí)行哪個異步任務(wù),異步任務(wù)的執(zhí)行順序就和EventLoop機制,即微任務(wù)和宏任務(wù)相關(guān)。
異步執(zhí)行的運行機制:
- 首先執(zhí)行主線程上的代碼,形成執(zhí)行棧,遇到異步任務(wù)就將它放入任務(wù)隊列中。
- 主線程執(zhí)行完畢,系統(tǒng)會查看任務(wù)隊列,某個任務(wù)可以執(zhí)行時,就進入執(zhí)行棧中開始執(zhí)行。(如果有微任務(wù)就執(zhí)行所有的微任務(wù),沒有微任務(wù)就執(zhí)行下一個宏任務(wù), 每個宏任務(wù)執(zhí)行完都要清空所有的微任務(wù))。
2.2 異步任務(wù)之微任務(wù)與宏任務(wù)
js是單線程的,所有的異步任務(wù)都被放入到任務(wù)隊列當(dāng)中,任務(wù)隊列又分為兩類:微任務(wù)和宏任務(wù)。
微任務(wù):
process.nextTick(先于promise執(zhí)行)
promise
Object.observe
MutaionObserver
宏任務(wù):
setTimeout
setInterval
setImmediate
IO
UI rerendering
三 Event Loop
主線程從任務(wù)隊列中讀取任務(wù)的過程是循環(huán)的,所以整個的運行機制被稱為 Event Loop(事件循環(huán))。
例子:
console.log('main1');
process.nextTick(function() {
console.log('process.nextTick1');
});
setTimeout(function() {
console.log('setTimeout');
process.nextTick(function() {
console.log('process.nextTick2');
});
}, 0);
new Promise(function(resolve, reject) {
console.log('promise');
resolve();
}).then(function() {
console.log('promise then');
});
console.log('main2');
結(jié)果:
- 開始執(zhí)行代碼輸出main1, process.nextTick放到執(zhí)行棧末尾,setTimeout 放到宏任務(wù),new promise 立即執(zhí)行輸出promise,then方法放到微任務(wù),執(zhí)行最后一句代碼輸出main2。
- 主線程執(zhí)行完畢,開始清空微任務(wù),執(zhí)行process.nextTick輸出process.nextTick1, 在執(zhí)行then輸出promise then。
- 執(zhí)行下次event loop,執(zhí)行setTimeout輸出 setTimeout,里面的 process.nextTick放到當(dāng)前執(zhí)行棧末尾,當(dāng)前宏任務(wù)執(zhí)行完畢,執(zhí)行微任務(wù),輸出process.nextTick2。
四 node中的Event Loop
Node的執(zhí)行過程
- v8引擎解析js腳本
- 調(diào)用node API
- libuv庫執(zhí)行node api, 將不同的任務(wù)分給不同的線程執(zhí)行,以異步的形式將結(jié)果返回給v8引擎
- v8引擎將結(jié)果返回給用戶
Node的EventLoop的過程
Node的EventLoop由6個任務(wù)階段組成,每個階段都有自己的任務(wù)隊列,每當(dāng)進入某個階段,都會從所屬的隊列中取出任務(wù)來執(zhí)行,當(dāng)隊列為空或者被執(zhí)行任務(wù)的數(shù)量達到系統(tǒng)的最大數(shù)量時,進入下一階段。這六個階段都執(zhí)行完畢稱為一輪循環(huán)。
六個階段具體而言:
- timers:執(zhí)行setTimeout() 和 setInterval()中到期的callback。
- I/O callbacks:上一輪循環(huán)中有少數(shù)的I/Ocallback會被延遲到這一輪的這一階段執(zhí)行
- idle, prepare:僅內(nèi)部使用,process.nextTick就屬于這一類
- poll:最為重要的階段,執(zhí)行I/O callback,在適當(dāng)?shù)臈l件下會阻塞在這個階段
- check:執(zhí)行setImmediate()的callback
-
close callbacks:執(zhí)行close事件的callback,例如socket.on("close",func)
Node的EventLoop.png
參考文章:
https://www.cnblogs.com/wuguanglin/p/EventLoop.html(Node的EventLoop機制講的比較好)
