
一、Event Loop是什么
Event Loop即事件循環(huán),是指瀏覽器或Node.js的一種解決javaScript單線程運行時不會阻塞的一種機制,也就是我們經(jīng)常使用異步的原理。
Event Loop是一個執(zhí)行模型,在不同的地方有不同的實現(xiàn),瀏覽器和Node.js基于不同的技術(shù)實現(xiàn)了各自的Event Loop。
二、宏任務(wù)和微任務(wù)
宏任務(wù),macrotask,也叫tasks。一些異步任務(wù)的回調(diào)會依次進入macrotask queue,還有一部分會進入其他的隊列,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
- setTimeout
- setInterval
- setImmediate(Node和最新版本IE獨有)
- I/O
- requestAnimationFrame
- requestIdleCallback
微任務(wù),microtask, 也叫jobs。另一些異步任務(wù)的回調(diào)會依次進入microtask queue,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
- process.nextTick(Node獨有)
- Promise.then
- Object.observe
- MutationObserver
(注:這里只針對瀏覽器和Node.js)
三、Event Loop運行機制
瀏覽器的Event Loop的執(zhí)行過程:

2.執(zhí)行微任務(wù)隊列中的所有任務(wù)。
3.開始執(zhí)行macrotask宏任務(wù),從宏任務(wù)隊列中取一個任務(wù)出來執(zhí)行,然后又執(zhí)行所有的微任務(wù),執(zhí)行的流程為:執(zhí)行一個宏任務(wù) -> 步驟2 -> 執(zhí)行一個宏任務(wù) -> 步驟2 -> 執(zhí)行一個宏任務(wù)...
Node.js的Event Loop的執(zhí)行過程:

2.執(zhí)行所有microtask微任務(wù),先執(zhí)行Next Tick Queue中的所有任務(wù),從Next Tick Queue中依次取出任務(wù)放入調(diào)用棧中執(zhí)行,再執(zhí)行Other Microtask Queue中的所有任務(wù),也是從Other Microtask Queue中依次取出任務(wù)放入調(diào)用棧中執(zhí)行。
3.開始執(zhí)行macrotask宏任務(wù),共6個階段,從第1個階段開始執(zhí)行相應(yīng)每一階段macrotask中的所有任務(wù)(在瀏覽器的Event Loop中是每次只取宏任務(wù)隊列中的一個任務(wù)出來執(zhí)行,然后又執(zhí)行所有的微任務(wù)),每一個階段的macrotask執(zhí)行完畢后,又開始執(zhí)行所有微任務(wù),執(zhí)行的流程為:Timers Queue -> 步驟2 -> I/O Queue -> 步驟2 -> Check Queue -> 步驟2 -> Close Callback Queue -> 步驟2 -> Timers Queue ...
注意:在較新版本11.0中, Node.js為了向瀏覽器靠齊,對底部進行了修改,Node11及之后版本已經(jīng)把在timer階段的setTimeout,setInterval...和在check階段的setImmediate都修改為一旦執(zhí)行完一個階段里的一個任務(wù)就立刻執(zhí)行微任務(wù)隊列。
四、細節(jié)特性
NodeJS中微任務(wù)有兩種,分別是process.nextTick和promise.then,那么這兩個誰先執(zhí)行呢?
Promise.resolve(‘123’).then(res=>{console.log(res)})
Process.nextTick(()=>{console.log(‘nextTick’)})
// 運行結(jié)果:
// nextTick
// 123
解釋:
promise.then雖然和process.nextTick一樣,都將回調(diào)函數(shù)注冊到microtask微任務(wù)中,但優(yōu)先級不一樣,process.nextTick的microtask queue總是優(yōu)先于promise的microtask queue執(zhí)行。
setTimeout和setImmediate
在Node中,setTimeout和setImmediate執(zhí)行順序不固定 取決于Node的準(zhǔn)備時間。
setTimeout(() => {
console.log(‘setTimeout’)
}, 0)
setImmediate(() => {
console.log(‘setImmediate’)
})
// 運行結(jié)果:
// setImmediate
// setTimeout
// 或者
// setTimeout
// setImmediate
解釋:
setTimeout/setInterval的第二個參數(shù)取值范圍是:[1, 2^31 - 1],如果超過這個范圍則會初始化為1,即setTimeout(fn, 0) === setTimeout(fn, 1)。
我們知道setTimeout的回調(diào)函數(shù)在timer階段執(zhí)行,setImmediate的回調(diào)函數(shù)在check階段執(zhí)行,Event Loop開始會先檢查timer階段,但是在開始之前到timer階段會消耗一定時間,所以就會出現(xiàn)兩種情況:
1.timer前的準(zhǔn)備時間超過1ms,滿足loop(time >= 1),則執(zhí)行timer階段(setTimeout)的回調(diào)函數(shù)
2.timer前的準(zhǔn)備時間小于1ms,則不滿足loop(time < 1),會先執(zhí)行check階段(setImmediate)的回調(diào)函數(shù),下一次Event Loop執(zhí)行timer階段(setTimeout)的回調(diào)函數(shù)。
五、面試題解析
console.log('1');
setTimeout(function(){
console.log('2');
process.nextTick(function(){
console.log('3')
})
new Promise(function(resolve){
console.log('4');
resolve();
}).then(function(){
console.log('5');
})
})
new Promise(function(resolve){
console.log('6');
resolve();
}).then(function(){
console.log('7');
})
process.nextTick(function(){
console.log('8');
})
setTimeout(function(){
console.log('9');
process.nextTick(function(){
console.log('10')
})
new Promise(function(resolve){
console.log('11');
resolve();
}).then(function(){
console.log('12');
})
})
console.log('13');
問:將以上代碼放在Node環(huán)境中(v11.0以下)運行,打印結(jié)果是什么?
答:1 6 13 8 7 2 4 9 11 3 10 5 12
解析:先執(zhí)行同步代碼,new Promise中的函數(shù)的代碼是同步代碼,Promise.then方法中函數(shù)的代碼才是異步的,所以第一輪循環(huán)同步代碼打印結(jié)果為:1 6 13,然后再看第一輪循環(huán)的微任務(wù),微任務(wù)總是會追加到本輪循環(huán)的末尾執(zhí)行,微任務(wù)中先執(zhí)行process.nextTick,再執(zhí)行Promise.then,所以第一輪循環(huán)打印結(jié)果為:1 6 13 8 7,此時第一輪微任務(wù)全都執(zhí)行完了,就該進入第二輪循環(huán)執(zhí)行宏任務(wù)了,宏任務(wù)分6個階段,這里只有Timer,所以打印結(jié)果為:1 6 13 8 7 2 4 9 11,然后執(zhí)行第二輪的微任務(wù),最后的打印結(jié)果為:1 6 13 8 7 2 4 9 11 3 10 5 12。
六、總結(jié)
1.Promise構(gòu)造函數(shù)里的代碼是同步執(zhí)行的,Promise.then里的代碼才是異步的。
2.不同環(huán)境中Event Loop的運行機制不同,所以不同環(huán)境中JS的運行結(jié)果也有可能不一致。
3.Node.js可以理解成有4個宏任務(wù)隊列和2個微任務(wù)隊列,但是執(zhí)行宏任務(wù)時有6個階段。
結(jié)語:這篇文章中只是介紹了像setTimeout和setInterval這些進入macrotask queue隊列的宏任務(wù)的執(zhí)行順序,想要了解更多,比如requestAnimationFrame和requestIdleCallback的執(zhí)行時機,可以看我的另一篇文章(requestAnimationFrame和requestIdleCallback是宏任務(wù)還是微任務(wù)), 希望本文章能對你有所幫助,文中有哪里不對的地方,歡迎大家指正,最后感謝大家的支持。
更多個人文章