前言
我在學(xué)習(xí)瀏覽器和NodeJS的Event Loop時(shí)看了大量的文章,那些文章都寫的很好,但是往往是每篇文章有那么幾個(gè)關(guān)鍵的點(diǎn),很多篇文章湊在一起綜合來看,才可以對(duì)這些概念有較為深入的理解。
于是,我在看了大量文章之后,想要寫這么一篇博客,不采用官方的描述,結(jié)合自己的理解以及示例代碼,用最通俗的語言表達(dá)出來。希望大家可以通過這篇文章,了解到Event Loop到底是一種什么機(jī)制,瀏覽器和NodeJS的Event Loop又有什么區(qū)別。如果在文中出現(xiàn)書寫錯(cuò)誤的地方,歡迎大家留言一起探討。
(PS:說到Event Loop肯定會(huì)提到Promise,我根據(jù)Promise A+規(guī)范自己實(shí)現(xiàn)了一個(gè)簡(jiǎn)易Promise庫,源碼放到Github上,大家有需要的可以當(dāng)做參考,后續(xù)我也會(huì)也寫一篇博客來講Promise,如果對(duì)你有用,就請(qǐng)給個(gè)Star吧~)
正文 Event Loop是什么event loop是一個(gè)執(zhí)行模型,在不同的地方有不同的實(shí)現(xiàn)。瀏覽器和NodeJS基于不同的技術(shù)實(shí)現(xiàn)了各自的Event Loop。
瀏覽器的Event Loop是在html5的規(guī)范中明確定義。
NodeJS的Event Loop是基于libuv實(shí)現(xiàn)的。可以參考Node的官方文檔以及l(fā)ibuv的官方文檔。
libuv已經(jīng)對(duì)Event Loop做出了實(shí)現(xiàn),而HTML5規(guī)范中只是定義了瀏覽器中Event Loop的模型,具體的實(shí)現(xiàn)留給了瀏覽器廠商。
宏隊(duì)列和微隊(duì)列宏隊(duì)列,macrotask,也叫tasks。 一些異步任務(wù)的回調(diào)會(huì)依次進(jìn)入macro task queue,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
setTimeout
setInterval
setImmediate (Node獨(dú)有)
requestAnimationFrame (瀏覽器獨(dú)有)
I/O
UI rendering (瀏覽器獨(dú)有)
微隊(duì)列,microtask,也叫jobs。 另一些異步任務(wù)的回調(diào)會(huì)依次進(jìn)入micro task queue,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
process.nextTick (Node獨(dú)有)
Promise
Object.observe
MutationObserver
(注:這里只針對(duì)瀏覽器和NodeJS)
瀏覽器的Event Loop我們先來看一張圖,再看完這篇文章后,請(qǐng)返回來再仔細(xì)看一下這張圖,相信你會(huì)有更深的理解。
這張圖將瀏覽器的Event Loop完整的描述了出來,我來講執(zhí)行一個(gè)JavaScript代碼的具體流程:
執(zhí)行全局Script同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句(比如setTimeout等);
全局Script代碼執(zhí)行完畢后,調(diào)用棧Stack會(huì)清空;
從微隊(duì)列microtask queue中取出位于隊(duì)首的回調(diào)任務(wù),放入調(diào)用棧Stack中執(zhí)行,執(zhí)行完后microtask queue長(zhǎng)度減1;
繼續(xù)取出位于隊(duì)首的任務(wù),放入調(diào)用棧Stack中執(zhí)行,以此類推,直到直到把microtask queue中的所有任務(wù)都執(zhí)行完畢。注意,如果在執(zhí)行microtask的過程中,又產(chǎn)生了microtask,那么會(huì)加入到隊(duì)列的末尾,也會(huì)在這個(gè)周期被調(diào)用執(zhí)行;
microtask queue中的所有任務(wù)都執(zhí)行完畢,此時(shí)microtask queue為空隊(duì)列,調(diào)用棧Stack也為空;
取出宏隊(duì)列macrotask queue中位于隊(duì)首的任務(wù),放入Stack中執(zhí)行;
執(zhí)行完畢后,調(diào)用棧Stack為空;
重復(fù)第3-7個(gè)步驟;
重復(fù)第3-7個(gè)步驟;
......
可以看到,這就是瀏覽器的事件循環(huán)Event Loop
這里歸納3個(gè)重點(diǎn):
宏隊(duì)列macrotask一次只從隊(duì)列中取一個(gè)任務(wù)執(zhí)行,執(zhí)行完后就去執(zhí)行微任務(wù)隊(duì)列中的任務(wù);
微任務(wù)隊(duì)列中所有的任務(wù)都會(huì)被依次取出來執(zhí)行,知道m(xù)icrotask queue為空;
圖中沒有畫UI rendering的節(jié)點(diǎn),因?yàn)檫@個(gè)是由瀏覽器自行判斷決定的,但是只要執(zhí)行UI rendering,它的節(jié)點(diǎn)是在執(zhí)行完所有的microtask之后,下一個(gè)macrotask之前,緊跟著執(zhí)行UI render。
好了,概念性的東西就這么多,來看幾個(gè)示例代碼,測(cè)試一下你是否掌握了:
console.log(1); setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); new Promise((resolve, reject) => { console.log(4) resolve(5) }).then((data) => { console.log(data); }) setTimeout(() => { console.log(6); }) console.log(7);
這里結(jié)果會(huì)是什么呢?運(yùn)用上面了解到的知識(shí),先自己做一下試試看。
// 正確答案 1 4 7 5 2 3 6
你答對(duì)了嗎?
我們來分析一下整個(gè)流程:
執(zhí)行全局Script代碼
Step 1
console.log(1)
Stack Queue: [console]
Macrotask Queue: []
Microtask Queue: []
打印結(jié)果:
1
Step 2
setTimeout(() => { // 這個(gè)回調(diào)函數(shù)叫做callback1,setTimeout屬于macrotask,所以放到macrotask queue中 console.log(2); Promise.resolve().then(() => { console.log(3) }); });
Stack Queue: [setTimeout]
Macrotask Queue: [callback1]
Microtask Queue: []
打印結(jié)果:
1
Step 3
new Promise((resolve, reject) => { // 注意,這里是同步執(zhí)行的,如果不太清楚,可以去看一下我開頭自己實(shí)現(xiàn)的promise啦~~ console.log(4) resolve(5) }).then((data) => { // 這個(gè)回調(diào)函數(shù)叫做callback2,promise屬于microtask,所以放到microtask queue中 console.log(data); })
Stack Queue: [promise]
Macrotask Queue: [callback1]
Microtask Queue: [callback2]
打印結(jié)果:
1
4
Step 5
setTimeout(() => { // 這個(gè)回調(diào)函數(shù)叫做callback3,setTimeout屬于macrotask,所以放到macrotask queue中 console.log(6); })
Stack Queue: [setTimeout]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印結(jié)果:
1
4
Step 6
console.log(7)
Stack Queue: [console]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印結(jié)果:
1
4
7
好啦,全局Script代碼執(zhí)行完了,進(jìn)入下一個(gè)步驟,從microtask queue中依次取出任務(wù)執(zhí)行,直到microtask queue隊(duì)列為空。
Step 7
console.log(data) // 這里data是Promise的決議值5
Stack Queue: [callback2]
Macrotask Queue: [callback1, callback3]
Microtask Queue: []
打印結(jié)果:
1
4
7
5
這里microtask queue中只有一個(gè)任務(wù),執(zhí)行完后開始從宏任務(wù)隊(duì)列macrotask queue中取位于隊(duì)首的任務(wù)執(zhí)行
Step 8
console.log(2)
Stack Queue: [callback1]
Macrotask Queue: [callback3]
Microtask Queue: []
打印結(jié)果:
1
4
7
5
2
但是,執(zhí)行callback1的時(shí)候又遇到了另一個(gè)Promise,Promise異步執(zhí)行完后在microtask queue中又注冊(cè)了一個(gè)callback4回調(diào)函數(shù)
Step 9
Promise.resolve().then(() => { // 這個(gè)回調(diào)函數(shù)叫做callback4,promise屬于microtask,所以放到microtask queue中 console.log(3) });
Stack Queue: [promise]
Macrotask v: [callback3]
Microtask Queue: [callback4]
打印結(jié)果:
1
4
7
5
2
取出一個(gè)宏任務(wù)macrotask執(zhí)行完畢,然后再去微任務(wù)隊(duì)列microtask queue中依次取出執(zhí)行
Step 10
console.log(3)
Stack Queue: [callback4]
Macrotask Queue: [callback3]
Microtask Queue: []
打印結(jié)果:
1
4
7
5
2
3
微任務(wù)隊(duì)列全部執(zhí)行完,再去宏任務(wù)隊(duì)列中取第一個(gè)任務(wù)執(zhí)行
Step 11
console.log(6)
Stack Queue: [callback3]
Macrotask Queue: []
Microtask Queue: []
打印結(jié)果:
1
4
7
5
2
3
6
以上,全部執(zhí)行完后,Stack Queue為空,Macrotask Queue為空,Micro Queue為空
Stack Queue: []
Macrotask Queue: []
Microtask Queue: []
最終打印結(jié)果:
1
4
7
5
2
3
6
因?yàn)槭堑谝粋€(gè)例子,所以這里分析的比較詳細(xì),大家仔細(xì)看一下,接下來我們?cè)賮硪粋€(gè)例子:
console.log(1); setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); new Promise((resolve, reject) => { console.log(4) resolve(5) }).then((data) => { console.log(data); Promise.resolve().then(() => { console.log(6) }).then(() => { console.log(7) setTimeout(() => { console.log(8) }, 0); }); }) setTimeout(() => { console.log(9); }) console.log(10);
最終輸出結(jié)果是什么呢?參考前面的例子,好好想一想......
// 正確答案 1 4 10 5 6 7 2 3 9 8
相信大家都答對(duì)了,這里的關(guān)鍵在前面已經(jīng)提過:
在執(zhí)行微隊(duì)列microtask queue中任務(wù)的時(shí)候,如果又產(chǎn)生了microtask,那么會(huì)繼續(xù)添加到隊(duì)列的末尾,也會(huì)在這個(gè)周期執(zhí)行,直到microtask queue為空停止。
注:當(dāng)然如果你在microtask中不斷的產(chǎn)生microtask,那么其他宏任務(wù)macrotask就無法執(zhí)行了,但是這個(gè)操作也不是無限的,拿NodeJS中的微任務(wù)process.nextTick()來說,它的上限是1000個(gè),后面我們會(huì)講到。
瀏覽器的Event Loop就說到這里,下面我們看一下NodeJS中的Event Loop,它更復(fù)雜一些,機(jī)制也不太一樣。
NodeJS中的Event Loop libuv先來看一張libuv的結(jié)構(gòu)圖:
NodeJS中的宏隊(duì)列和微隊(duì)列NodeJS的Event Loop中,執(zhí)行宏隊(duì)列的回調(diào)任務(wù)有6個(gè)階段,如下圖:
各個(gè)階段執(zhí)行的任務(wù)如下:
timers階段:這個(gè)階段執(zhí)行setTimeout和setInterval預(yù)定的callback
I/O callback階段:執(zhí)行除了close事件的callbacks、被timers設(shè)定的callbacks、setImmediate()設(shè)定的callbacks這些之外的callbacks
idle, prepare階段:僅node內(nèi)部使用
poll階段:獲取新的I/O事件,適當(dāng)?shù)臈l件下node將阻塞在這里
check階段:執(zhí)行setImmediate()設(shè)定的callbacks
close callbacks階段:執(zhí)行socket.on("close", ....)這些callbacks
NodeJS中宏隊(duì)列主要有4個(gè)
由上面的介紹可以看到,回調(diào)事件主要位于4個(gè)macrotask queue中:
Timers Queue
IO Callbacks Queue
Check Queue
Close Callbacks Queue
這4個(gè)都屬于宏隊(duì)列,但是在瀏覽器中,可以認(rèn)為只有一個(gè)宏隊(duì)列,所有的macrotask都會(huì)被加到這一個(gè)宏隊(duì)列中,但是在NodeJS中,不同的macrotask會(huì)被放置在不同的宏隊(duì)列中。
NodeJS中微隊(duì)列主要有2個(gè):
Next Tick Queue:是放置process.nextTick(callback)的回調(diào)任務(wù)的
Other Micro Queue:放置其他microtask,比如Promise等
在瀏覽器中,也可以認(rèn)為只有一個(gè)微隊(duì)列,所有的microtask都會(huì)被加到這一個(gè)微隊(duì)列中,但是在NodeJS中,不同的microtask會(huì)被放置在不同的微隊(duì)列中。
具體可以通過下圖加深一下理解:
大體解釋一下NodeJS的Event Loop過程:
執(zhí)行全局Script的同步代碼
執(zhí)行microtask微任務(wù),先執(zhí)行所有Next Tick Queue中的所有任務(wù),再執(zhí)行Other Microtask Queue中的所有任務(wù)
開始執(zhí)行macrotask宏任務(wù),共6個(gè)階段,從第1個(gè)階段開始執(zhí)行相應(yīng)每一個(gè)階段macrotask中的所有任務(wù),注意,這里是所有每個(gè)階段宏任務(wù)隊(duì)列的所有任務(wù),在瀏覽器的Event Loop中是只取宏隊(duì)列的第一個(gè)任務(wù)出來執(zhí)行,每一個(gè)階段的macrotask任務(wù)執(zhí)行完畢后,開始執(zhí)行微任務(wù),也就是步驟2
Timers Queue -> 步驟2 -> I/O Queue -> 步驟2 -> Check Queue -> 步驟2 -> Close Callback Queue -> 步驟2 -> Timers Queue ......
這就是Node的Event Loop
關(guān)于NodeJS的macrotask queue和microtask queue,我畫了兩張圖,大家作為參考:
好啦,概念理解了我們通過幾個(gè)例子來實(shí)戰(zhàn)一下:
第一個(gè)例子
console.log("start"); setTimeout(() => { // callback1 console.log(111); setTimeout(() => { // callback2 console.log(222); }, 0); setImmediate(() => { // callback3 console.log(333); }) process.nextTick(() => { // callback4 console.log(444); }) }, 0); setImmediate(() => { // callback5 console.log(555); process.nextTick(() => { // callback6 console.log(666); }) }) setTimeout(() => { // callback7 console.log(777); process.nextTick(() => { // callback8 console.log(888); }) }, 0); process.nextTick(() => { // callback9 console.log(999); }) console.log("end");
請(qǐng)運(yùn)用前面學(xué)到的知識(shí),仔細(xì)分析一下......
// 正確答案 start end 999 111 777 444 888 555 333 666 222
更新 2018.9.20
上面這段代碼你執(zhí)行的結(jié)果可能會(huì)有多種情況,原因解釋如下。
setTimeout(fn, 0)不是嚴(yán)格的0,一般是setTimeout(fn, 3)或什么,會(huì)有一定的延遲時(shí)間,當(dāng)setTimeout(fn, 0)和setImmediate(fn)出現(xiàn)在同一段同步代碼中時(shí),就會(huì)存在兩種情況。
第1種情況:同步代碼執(zhí)行完了,Timer還沒到期,setImmediate回調(diào)先注冊(cè)到Check Queue中,開始執(zhí)行微隊(duì)列,然后是宏隊(duì)列,先從Timers Queue中開始,發(fā)現(xiàn)沒回調(diào),往下走直到Check Queue中有回調(diào),執(zhí)行,然后timer到期(只要在執(zhí)行完Timer Queue后到期效果就都一樣),timer回調(diào)注冊(cè)到Timers Queue中,下一輪循環(huán)執(zhí)行到Timers Queue中才能執(zhí)行那個(gè)timer 回調(diào);所以,這種情況下,setImmediate(fn)回調(diào)先于setTimeout(fn, 0)回調(diào)執(zhí)行。
第2種情況:同步代碼還沒執(zhí)行完,timer先到期,timer回調(diào)先注冊(cè)到Timers Queue中,執(zhí)行到setImmediate了,它的回調(diào)再注冊(cè)到Check Queue中。 然后,同步代碼執(zhí)行完了,執(zhí)行微隊(duì)列,然后開始先執(zhí)行Timers Queue,先執(zhí)行Timer 回調(diào),再到Check Queue,執(zhí)行setImmediate回調(diào);所以,這種情況下,setTimeout(fn, 0)回調(diào)先于setImmediate(fn)回調(diào)執(zhí)行。
所以,在同步代碼中同時(shí)調(diào)setTimeout(fn, 0)和setImmediate情況是不確定的,但是如果把他們放在一個(gè)IO的回調(diào),比如readFile("xx", function () {// ....})回調(diào)中,那么IO回調(diào)是在IO Queue中,setTimeout到期回調(diào)注冊(cè)到Timers Queue,setImmediate回調(diào)注冊(cè)到Check Queue,IO Queue執(zhí)行完到Check Queue,timer Queue得到下個(gè)周期,所以setImmediate回調(diào)這種情況下肯定比setTimeout(fn, 0)回調(diào)先執(zhí)行。
綜上,這個(gè)例子是不太好的,setTimeout(fn, 0)和setImmediate(fn)如果想要保證結(jié)果唯一,就放在一個(gè)IO Callback中吧,上面那段代碼可以把所有它倆同步執(zhí)行的代碼都放在一個(gè)IO Callback中,結(jié)果就唯一了。
更新結(jié)束
你答對(duì)了嗎?我們來一起分析一下:
執(zhí)行全局Script代碼,先打印start,向下執(zhí)行,將setTimeout的回調(diào)callback1注冊(cè)到Timers Queue中,再向下執(zhí)行,將setImmediate的回調(diào)callback5注冊(cè)到Check Queue中,接著向下執(zhí)行,將setTimeout的回調(diào)callback7注冊(cè)到Timers Queue中,繼續(xù)向下,將process.nextTick的回調(diào)callback9注冊(cè)到微隊(duì)列Next Tick Queue中,最后一步打印end。此時(shí),各個(gè)隊(duì)列的回調(diào)情況如下:
宏隊(duì)列
Timers Queue: [callback1, callback7]
Check Queue: [callback5]
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: [callback9]
Other Microtask Queue: []
打印結(jié)果
start
end
全局Script執(zhí)行完了,開始依次執(zhí)行微任務(wù)Next Tick Queue中的全部回調(diào)任務(wù)。此時(shí)Next Tick Queue中只有一個(gè)callback9,將其取出放入調(diào)用棧中執(zhí)行,打印999。
宏隊(duì)列
Timers Queue: [callback1, callback7]
Check Queue: [callback5]
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: []
Other Microtask Queue: []
打印結(jié)果
start
end
999
開始依次執(zhí)行6個(gè)階段各自宏隊(duì)列中的所有任務(wù),先執(zhí)行第1個(gè)階段Timers Queue中的所有任務(wù),先取出callback1執(zhí)行,打印111,callback1函數(shù)繼續(xù)向下,依次把callback2放入Timers Queue中,把callback3放入Check Queue中,把callback4放入Next Tick Queue中,然后callback1執(zhí)行完畢。再取出Timers Queue中此時(shí)排在首位的callback7執(zhí)行,打印777,把callback8放入Next Tick Queue中,執(zhí)行完畢。此時(shí),各隊(duì)列情況如下:
宏隊(duì)列
Timers Queue: [callback2]
Check Queue: [callback5, callback3]
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: [callback4, callback8]
Other Microtask Queue: []
打印結(jié)果
start
end
999
111
777
6個(gè)階段每階段的宏任務(wù)隊(duì)列執(zhí)行完畢后,都會(huì)開始執(zhí)行微任務(wù),此時(shí),先取出Next Tick Queue中的所有任務(wù)執(zhí)行,callback4開始執(zhí)行,打印444,然后callback8開始執(zhí)行,打印888,Next Tick Queue執(zhí)行完畢,開始執(zhí)行Other Microtask Queue中的任務(wù),因?yàn)槔锩鏋榭?,所以繼續(xù)向下。
宏隊(duì)列
Timers Queue: [callback2]
Check Queue: [callback5, callback3]
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: []
Other Microtask Queue: []
打印結(jié)果
start
end
999
111
777
444
888
第2個(gè)階段IO Callback Queue隊(duì)列為空,跳過,第3和第4個(gè)階段一般是Node內(nèi)部使用,跳過,進(jìn)入第5個(gè)階段Check Queue。取出callback5執(zhí)行,打印555,把callback6放入Next Tick Queue中,執(zhí)行callback3,打印333。
宏隊(duì)列
Timers Queue: [callback2]
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: [callback6]
Other Microtask Queue: []
打印結(jié)果
start
end
999
111
777
444
888
555
333
執(zhí)行微任務(wù)隊(duì)列,先執(zhí)行Next Tick Queue,取出callback6執(zhí)行,打印666,執(zhí)行完畢,因?yàn)镺ther Microtask Queue為空,跳過。
宏隊(duì)列
Timers Queue: [callback2]
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: [callback6]
Other Microtask Queue: []
打印結(jié)果
start
end
999
111
777
444
888
555
333
執(zhí)行第6個(gè)階段Close Callback Queue中的任務(wù),為空,跳過,好了,此時(shí)一個(gè)循環(huán)已經(jīng)結(jié)束。進(jìn)入下一個(gè)循環(huán),執(zhí)行第1個(gè)階段Timers Queue中的所有任務(wù),取出callback2執(zhí)行,打印222,完畢。此時(shí),所有隊(duì)列包括宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列都為空,不再打印任何東西。
宏隊(duì)列
Timers Queue: []
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微隊(duì)列
Next Tick Queue: [callback6]
Other Microtask Queue: []
最終結(jié)果
start
end
999
111
777
444
888
555
333
666
222
以上就是這道題目的詳細(xì)分析,如果沒有明白,一定要多看幾次。
下面引入Promise再來看一個(gè)例子:
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("7"); resolve(); }).then(function() { console.log("8") }) process.nextTick(function() { console.log("6"); }) setTimeout(function() { console.log("9"); process.nextTick(function() { console.log("10"); }) new Promise(function(resolve) { console.log("11"); resolve(); }).then(function() { console.log("12") }) })
大家仔細(xì)分析,相比于上一個(gè)例子,這里由于存在Promise,所以O(shè)ther Microtask Queue中也會(huì)有回調(diào)任務(wù)的存在,執(zhí)行到微任務(wù)階段時(shí),先執(zhí)行Next Tick Queue中的所有任務(wù),再執(zhí)行Other Microtask Queue中的所有任務(wù),然后才會(huì)進(jìn)入下一個(gè)階段的宏任務(wù)。明白了這一點(diǎn),相信大家都可以分析出來,下面直接給出正確答案,如有疑問,歡迎留言和我討論。
// 正確答案 1 7 6 8 2 4 9 11 3 10 5 12setTimeout 對(duì)比 setImmediate
setTimeout(fn, 0)在Timers階段執(zhí)行,并且是在poll階段進(jìn)行判斷是否達(dá)到指定的timer時(shí)間才會(huì)執(zhí)行
setImmediate(fn)在Check階段執(zhí)行
兩者的執(zhí)行順序要根據(jù)當(dāng)前的執(zhí)行環(huán)境才能確定:
如果兩者都在主模塊(main module)調(diào)用,那么執(zhí)行先后取決于進(jìn)程性能,順序隨機(jī)
如果兩者都不在主模塊調(diào)用,即在一個(gè)I/O Circle中調(diào)用,那么setImmediate的回調(diào)永遠(yuǎn)先執(zhí)行,因?yàn)闀?huì)先到Check階段
setImmediate 對(duì)比 process.nextTicksetImmediate(fn)的回調(diào)任務(wù)會(huì)插入到宏隊(duì)列Check Queue中
process.nextTick(fn)的回調(diào)任務(wù)會(huì)插入到微隊(duì)列Next Tick Queue中
process.nextTick(fn)調(diào)用深度有限制,上限是1000,而setImmedaite則沒有
總結(jié)瀏覽器的Event Loop和NodeJS的Event Loop是不同的,實(shí)現(xiàn)機(jī)制也不一樣,不要混為一談。
瀏覽器可以理解成只有1個(gè)宏任務(wù)隊(duì)列和1個(gè)微任務(wù)隊(duì)列,先執(zhí)行全局Script代碼,執(zhí)行完同步代碼調(diào)用棧清空后,從微任務(wù)隊(duì)列中依次取出所有的任務(wù)放入調(diào)用棧執(zhí)行,微任務(wù)隊(duì)列清空后,從宏任務(wù)隊(duì)列中只取位于隊(duì)首的任務(wù)放入調(diào)用棧執(zhí)行,注意這里和Node的區(qū)別,只取一個(gè),然后繼續(xù)執(zhí)行微隊(duì)列中的所有任務(wù),再去宏隊(duì)列取一個(gè),以此構(gòu)成事件循環(huán)。
NodeJS可以理解成有4個(gè)宏任務(wù)隊(duì)列和2個(gè)微任務(wù)隊(duì)列,但是執(zhí)行宏任務(wù)時(shí)有6個(gè)階段。先執(zhí)行全局Script代碼,執(zhí)行完同步代碼調(diào)用棧清空后,先從微任務(wù)隊(duì)列Next Tick Queue中依次取出所有的任務(wù)放入調(diào)用棧中執(zhí)行,再?gòu)奈⑷蝿?wù)隊(duì)列Other Microtask Queue中依次取出所有的任務(wù)放入調(diào)用棧中執(zhí)行。然后開始宏任務(wù)的6個(gè)階段,每個(gè)階段都將該宏任務(wù)隊(duì)列中的所有任務(wù)都取出來執(zhí)行(注意,這里和瀏覽器不一樣,瀏覽器只取一個(gè)),每個(gè)宏任務(wù)階段執(zhí)行完畢后,開始執(zhí)行微任務(wù),再開始執(zhí)行下一階段宏任務(wù),以此構(gòu)成事件循環(huán)。
MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(瀏覽器)、IO、UI rendering
Microtask包括: process.nextTick(Node)、Promise、Object.observe、MutationObserver
歡迎關(guān)注我的公眾號(hào) 參考鏈接不要混淆nodejs和瀏覽器中的event loop
node中的Event模塊
Promises, process.nextTick And setImmediate
瀏覽器和Node不同的事件循環(huán)
Tasks, microtasks, queues and schedules
理解事件循環(huán)淺析
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97392.html
摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實(shí)驗(yàn)室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類面試問題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...
摘要:事件完成,回調(diào)函數(shù)進(jìn)入。主線程從讀取回調(diào)函數(shù)并執(zhí)行。終于執(zhí)行完了,終于從進(jìn)入了主線程執(zhí)行。遇到,立即執(zhí)行。宏任務(wù)微任務(wù)第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個(gè)微任務(wù)和。事件循環(huán)事件循環(huán)是實(shí)現(xiàn)異步的一種方法,也是的執(zhí)行機(jī)制。 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發(fā)工作...
摘要:關(guān)于這部分有嚴(yán)格的文字定義,但本文的目的是用最小的學(xué)習(xí)成本徹底弄懂執(zhí)行機(jī)制,所以同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行場(chǎng)所,同步的進(jìn)入主線程,異步的進(jìn)入并注冊(cè)函數(shù)。宏任務(wù)微任務(wù)第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個(gè)微任務(wù)和。 不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發(fā)工作,我們經(jīng)常會(huì)遇到這樣的情況:給定的幾行代碼,我們需要知道其輸出內(nèi)容和順序。 因?yàn)閖avascr...
摘要:一直以來,對(duì)的執(zhí)行機(jī)制都是模棱兩可,知道今天看了文章這一次,徹底弄懂執(zhí)行機(jī)制和的規(guī)范和實(shí)現(xiàn),才對(duì)的執(zhí)行機(jī)制有了深入的理解,下面是我的學(xué)習(xí)總結(jié)。個(gè)要點(diǎn)是單線程語言是的執(zhí)行機(jī)制,為了實(shí)現(xiàn)主線程的不阻塞,就這么誕生了。 一直以來,對(duì)JS的執(zhí)行機(jī)制都是模棱兩可,知道今天看了文章—《這一次,徹底弄懂JavaScript執(zhí)行機(jī)制》和《Event Loop的規(guī)范和實(shí)現(xiàn)》,才對(duì)JS的執(zhí)行機(jī)制有了深入的...
摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡(jiǎn)的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...
閱讀 3736·2021-10-08 10:04
閱讀 1055·2019-08-30 15:54
閱讀 2336·2019-08-29 16:09
閱讀 1485·2019-08-29 15:41
閱讀 2417·2019-08-29 11:01
閱讀 1867·2019-08-26 13:51
閱讀 1187·2019-08-26 13:25
閱讀 1987·2019-08-26 13:24