hey,你的Event Loop
瀏覽器中的Event Loop
參考資料:并發(fā)模型與事件循環(huán)-MDN
JavaScript 運行機(jī)制詳解:再談Event Loop@阮老師
我們都知道JavaScript是單線程的,也就是說同一時間只能干一件事。這是因為JavaScript主要是用來操作DOM的,如果變成多線程,瀏覽器就懵逼了,不知道該聽誰的了。但是雖然js是單線程,但是完全可以模擬多線程,靠的就是Event Loop。
我們都知道js中的代碼分 同步 和 異步,所謂的 異步 其實就是不會阻塞我們的主線程,等待主線程的代碼執(zhí)行完畢才會執(zhí)行。callback setTimeout setInterval Promise ... 這些都是都是我們耳熟能詳?shù)?異步 代碼
如圖所示,js中的內(nèi)存分為 堆內(nèi)存(heap) 和 棧內(nèi)存(stack), 堆內(nèi)存 中存的是我們聲明的object類型的數(shù)據(jù),棧內(nèi)存 中存的是 基本數(shù)據(jù)類型 以及 函數(shù)執(zhí)行時的運行空間。我們的 同步 代碼就放在 執(zhí)行棧 中,那異步代碼呢?瀏覽器會將 dom事件 ajax setTimeout等異步代碼放到隊列中,等待執(zhí)行棧中的代碼都執(zhí)行完畢,才會執(zhí)行隊列中的代碼,是不是有點像發(fā)布訂閱模式。
console.log(1); setTimeout(() => { console.log(2); }, 0); console.log(3); 復(fù)制代碼
根據(jù)之前說的,setTimeout 會被放到隊列中,等待執(zhí)行棧中的代碼執(zhí)行完畢才會執(zhí)行,所以會輸出1, 3, 2
但是異步代碼也是有區(qū)別的:
console.log(1) setTimeout(() => { console.log(2) }, 0) Promise.resolve().then(() => { console.log(3) }) 復(fù)制代碼
輸出的永遠(yuǎn)是1, 3, 2, 也就是說 promise 在 setTimeout 之前執(zhí)行了。這是因為 異步任務(wù) 分為 微任務(wù)(microtask) 和 宏任務(wù)(task),執(zhí)行的順序是 執(zhí)行棧中的代碼 => 微任務(wù) => 宏任務(wù)。
執(zhí)行棧
執(zhí)行棧中的代碼永遠(yuǎn)最先執(zhí)行
微任務(wù)(microtask): promise MutationObserver...
當(dāng)
執(zhí)行棧中的代碼執(zhí)行完畢,會在執(zhí)行宏任務(wù)隊列之前先看看微任務(wù)隊列中有沒有任務(wù),如果有會先將微任務(wù)隊列中的任務(wù)清空才會去執(zhí)行宏任務(wù)隊列
宏任務(wù)(task): setTimeout setInterval setImmediate(IE專用) messageChannel...
等待
執(zhí)行棧和微任務(wù)隊列都執(zhí)行完畢才會執(zhí)行,并且在執(zhí)行完每一個宏任務(wù)之后,會去看看微任務(wù)隊列有沒有新添加的任務(wù),如果有,會先將微任務(wù)隊列中的任務(wù)清空,才會繼續(xù)執(zhí)行下一個宏任務(wù)
setTimeout(() => { console.log('timeout1') Promise.resolve().then(() => { console.log('promise1') }) Promise.resolve().then(() => { console.log('promise2') }) }, 100) setTimeout(() => { console.log('timeout2') Promise.resolve().then(() => { console.log('promise3') }) }, 200) 復(fù)制代碼
先將兩個
setTimeout塞到宏任務(wù)隊列中當(dāng)?shù)谝粋€
setTimeout1時間到了執(zhí)行的時候,首先打印timeout1,然后在微任務(wù)隊列中塞入promise1和promise2當(dāng)?shù)谝粋€
setTimeout1執(zhí)行完畢后,會去微任務(wù)隊列檢查是不是空的,他發(fā)現(xiàn)了有兩個promise,會把兩個promise按順序執(zhí)行完再去執(zhí)行下一個宏任務(wù)兩個
promise執(zhí)行完畢后會微任務(wù)隊列中沒有任務(wù)了,會去宏任務(wù)中執(zhí)行下一個任務(wù)setTimeout2當(dāng)
setTimeout2執(zhí)行的時候,先打印一個timeout2,然后又在微任務(wù)隊列中塞了一個promise2當(dāng)
setTimeout2執(zhí)行完畢后會去微任務(wù)隊列檢查,發(fā)現(xiàn)有一個promise2,會將promise2執(zhí)行會依次打印
timeout1 promise1 promise2 timeout2 promise3
Node中的Event Loop
我們都知道Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環(huán)境,也就是能夠讓js在服務(wù)端運行。但是Node中的Event Loop是用libuv模擬的,它將不同的任務(wù)分配給不同的線程,形成一個Event Loop,以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給V8引擎。
Node中的微任務(wù):
process.nextTric promise setImmediate...Node中的宏任務(wù):
setTimeout setInterval...
timers:執(zhí)行setTimeout() 和 setInterval()中到期的callback。
I/O callbacks:上一輪循環(huán)中有少數(shù)的I/Ocallback會被延遲到這一輪的這一階段執(zhí)行
idle, prepare:僅內(nèi)部使用
poll:最為重要的階段,執(zhí)行I/O callback,檢查有沒有timers到期 timer在適當(dāng)?shù)臈l件下會阻塞在這個階段
check:執(zhí)行setImmediate的callback
close callbacks:執(zhí)行close事件的callback,例如socket.on("close",func)
Node中的Event Loop會在每次切換隊列的時候 清空微任務(wù)隊列,也就會會將當(dāng)前隊列都執(zhí)行完,在進(jìn)入下一階段的時候檢查一下微任務(wù)中有沒有任務(wù)
setTimeout(() => { console.log('timeout1') Promise.resolve().then(() => { console.log('promise1') }) Promise.resolve().then(() => { console.log('promise2') }) }, 0) setTimeout(() => { console.log('timeout2') Promise.resolve().then(() => { console.log('promise3') }) }, 0) 復(fù)制代碼
先將兩個
setTimeout塞到宏任務(wù)隊列中當(dāng)?shù)谝粋€
setTimeout1時間到了執(zhí)行的時候,首先打印timeout1,然后在微任務(wù)隊列中塞入promise1和promise2當(dāng)?shù)谝粋€
setTimeout1執(zhí)行完畢后,繼續(xù)執(zhí)行下一個setTimeout2當(dāng)
setTimeout2執(zhí)行的時候,先打印一個timeout2,然后又在微任務(wù)隊列中塞了一個promise2當(dāng)前宏任務(wù)隊列清空,進(jìn)入下一階段,去檢查微任務(wù)隊列中有沒有任務(wù)
清空微任務(wù)隊列
在node環(huán)境中執(zhí)行 會依次打印
timeout1 timeout2 promise1 promise2 promise3
作者:asdff
來源:掘金
工程師必備
- 項目客服
- 培訓(xùn)客服
- 平臺客服
TOP




















