封面
概念
同步任務(wù)(Synchronous)
- 在主線(xiàn)程上排隊(duì)執(zhí)行的任務(wù)胖腾,只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)
異步任務(wù)(Asynchronous)
- 不進(jìn)入主線(xiàn)程是己,而是進(jìn)入“任務(wù)隊(duì)列”的任務(wù)跟伏,只有主線(xiàn)執(zhí)行棧清空,異步任務(wù)才進(jìn)入主線(xiàn)執(zhí)行棧執(zhí)行
任務(wù)隊(duì)列(Task Queue)
- 包含有異步任務(wù)的隊(duì)列销钝,包括“宏任務(wù)”與“微任務(wù)”
宏任務(wù)(Macrotasks / Task)
- 創(chuàng)建文檔對(duì)象有咨、解析 HTML琐簇、執(zhí)行主線(xiàn)程代碼(script)
- 執(zhí)行各種事件:頁(yè)面加載、輸入座享、點(diǎn)擊
-
setTimout
婉商,setInterval
,setImmediate
- I/O渣叛,Ajax丈秩,UI rendering
微任務(wù)(Microtasks / Jobs)
process.nextTick
Promise.then
-
Object.observe
(已廢棄) MutationObserver
事件循環(huán)(Event Loop)
瀏覽器下的事件循環(huán)
- 事件循環(huán)是js實(shí)現(xiàn)異步的一種方法,也是js的執(zhí)行機(jī)制
- JavaScript 主線(xiàn)程會(huì)在執(zhí)行棧清空后淳衙,讀取任務(wù)隊(duì)列蘑秽,入棧第一個(gè)宏任務(wù)饺著,主線(xiàn)程執(zhí)行完該任務(wù)后又會(huì)先檢查微任務(wù)隊(duì)列并完成里面的所有微任務(wù)积瞒,包括新創(chuàng)建的微任務(wù)饵较,完成一次事件循環(huán)。之后再次去讀取任務(wù)隊(duì)列诊霹,不斷循環(huán)
- 注意:
- 每次循環(huán)只會(huì)入棧一個(gè)宏任務(wù)缀雳,所以多個(gè)宏任務(wù)需要多次事件循環(huán)才能執(zhí)行完
- 每次循環(huán)會(huì)執(zhí)行所有的微任務(wù)渡嚣,所以每次循環(huán)結(jié)束后微任務(wù)隊(duì)列被清空
Node.JS下的事件循環(huán)
-
timers:
- 執(zhí)行
setTimeout
和setInterval
中到期的callback
- 執(zhí)行
-
I/O callbacks:
- 除了以下操作的回調(diào)函數(shù),其他的回調(diào)函數(shù)都在這個(gè)階段執(zhí)行肥印。
-
setTimeout
识椰,setInterval
,setImmediate
的callback
- 用于執(zhí)行
close
事件(關(guān)閉請(qǐng)求)的callback
深碱,例如socket.on('close', callback)
-
- 除了以下操作的回調(diào)函數(shù),其他的回調(diào)函數(shù)都在這個(gè)階段執(zhí)行肥印。
-
idle, prepare:
- libuv 內(nèi)部調(diào)用
-
poll:
- 最為重要的階段腹鹉,用于等待還未返回的 I/O 事件,比如服務(wù)器的回應(yīng)敷硅、用戶(hù)移動(dòng)鼠標(biāo)等等
- 這個(gè)階段的時(shí)間會(huì)比較長(zhǎng)种蘸。如果沒(méi)有其他異步任務(wù)要處理(比如到期的定時(shí)器),會(huì)一直停留在這個(gè)階段竞膳,等待 I/O 請(qǐng)求返回結(jié)果航瞭。
-
check:
- 執(zhí)行
setImmediate
的callback
- 執(zhí)行
-
close callbacks:
- 執(zhí)行
close
事件(關(guān)閉請(qǐng)求)的callback
,例如socket.on('close', callback)
- 執(zhí)行
- 事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過(guò)上述的階段坦辟。 每個(gè)階段都有自己的
callback
隊(duì)列刊侯,每當(dāng)進(jìn)入某個(gè)階段,都會(huì)從所屬的隊(duì)列中取出callback來(lái)執(zhí)行锉走,當(dāng)隊(duì)列為空或者被執(zhí)行callback的數(shù)量達(dá)到系統(tǒng)的最大數(shù)量時(shí)滨彻,進(jìn)入下一階段。這六個(gè)階段都執(zhí)行完畢稱(chēng)為一輪循環(huán) - 注意:
- 不同于瀏覽器的是挪蹭,在每個(gè)階段完成后亭饵,microTask隊(duì)列就會(huì)被執(zhí)行,而不是MacroTask任務(wù)完成后梁厉。
- 每個(gè)階段完成后辜羊,微任務(wù)隊(duì)列就會(huì)被執(zhí)行。
- 如果在timers階段執(zhí)行時(shí)創(chuàng)建了setImmediate則會(huì)在此輪循環(huán)的check階段執(zhí)行词顾,如果在timers階段創(chuàng)建了
setTimeout
八秃,由于timers已取出完畢,則會(huì)進(jìn)入下輪循環(huán)肉盹,check階段創(chuàng)建timers任務(wù)同理 - 遞歸的調(diào)用
process.nextTick
會(huì)導(dǎo)致 I/O starving昔驱,官方推薦使用setImmediate
Node.JS與瀏覽器下的差異
- 瀏覽器環(huán)境下,microtask 的任務(wù)隊(duì)列是每個(gè) macrotask 執(zhí)行完之后執(zhí)行
- Node.js中上忍,microtask 會(huì)在事件循環(huán)的各個(gè)階段之間執(zhí)行骤肛,也就是一個(gè)階段執(zhí)行完畢纳本,就會(huì)去執(zhí)行 microtask 隊(duì)列的任務(wù)
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
// 瀏覽器輸出:
// time1
// promise1
// time2
// promise2
// Node輸出:
// time1
// time2
// promise1
// promise2
定時(shí)器
setTimeout(callback, time)
- 經(jīng)過(guò)指定時(shí)間后,把要執(zhí)行的任務(wù)
callback
加入到任務(wù)隊(duì)列中 - 因?yàn)镴S是單線(xiàn)程腋颠,任務(wù)要一個(gè)一個(gè)執(zhí)行饮醇,如果前面的任務(wù)需要的時(shí)間太久,那么只能等著秕豫,導(dǎo)致真正的延遲時(shí)間可能遠(yuǎn)遠(yuǎn)大于指定時(shí)間(time ms)
setTimeout(callback, 0)
- 指定某個(gè)任務(wù)
callback
在主線(xiàn)程最早可得的空閑時(shí)間執(zhí)行朴艰,意思就是不用再等多少秒了,只要主線(xiàn)程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成混移,棧為空就馬上執(zhí)行 - 0ms 實(shí)際上是不可能的祠墅,在瀏覽器中
setTimeout()
/setInterval()
的每調(diào)用一次定時(shí)器的最小間隔 >=4ms,這通常是由于函數(shù)嵌套導(dǎo)致(嵌套層級(jí)達(dá)到一定深度)歌径,或者是由于已經(jīng)執(zhí)行的setInterval
的回調(diào)函數(shù)阻塞導(dǎo)致的 - 在 Node.JS 環(huán)境為 1ms毁嗦,但也取決于系統(tǒng)當(dāng)時(shí)的狀況
setTimeout(function () {
console.log("1");
}, 0)
console.log(2)
// 輸出 2 1
setInterval(callback, time)
- 每過(guò)指定時(shí)間(time ms),會(huì)有
callback
進(jìn)入任務(wù)隊(duì)列回铛。 - 若
callback
執(zhí)行時(shí)間超過(guò)了指定時(shí)間狗准,那么就會(huì)導(dǎo)致callback
連續(xù)執(zhí)行,完全看不出來(lái)有時(shí)間間隔了
setImmediate(callback)
- Node.JS 特有定時(shí)器茵肃,在事件循環(huán)的 check 階段執(zhí)行
process.nextTick(callback)
- Node.JS 特有定時(shí)器腔长,在事件循環(huán)各個(gè)階段結(jié)束后執(zhí)行
- 從技術(shù)上講,它不是事件循環(huán)的一部分
- 同循環(huán)下
process.nextTick
會(huì)優(yōu)于Promise.then
Promise.resolve().then(() => console.log(1));
process.nextTick(() => console.log(2));
// 輸出 2 1
注意
- 連續(xù)的
setTimeout
验残,setImmediate
在再 timer 階段的執(zhí)行順序是不確定的捞附,取決于系統(tǒng)當(dāng)時(shí)的狀況 - 但是把
setTimeout
,setImmediate
放到一個(gè) I/O 回調(diào)里面您没,就一定是setImmediate
先執(zhí)行鸟召,因?yàn)?poll 階段后面就是 check 階段
setImmediate(() => {
console.log('timer1')
Promise.resolve().then(function () {
console.log('promise1')
})
})
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function () {
console.log('promise2')
})
}, 0)
// Node輸出:
// timer1 timer2
// promise1 或者 promise2
// timer2 timer1
// promise2 promise1
fs.readFile('test.js', () => {
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
})
// 輸出 2 1
// 先進(jìn)入 I/O callbacks 階段,然后是 check 階段氨鹏,最后才是 下一次事件循環(huán)的 timers 階段欧募。因此,setImmediate 才會(huì)早于setTimeout 執(zhí)行仆抵。
示例
console.log(0)
new Promise(function(resolve) {
console.log(1);
resolve();
}).then(function() {
console.log(2)
})
setTimeout(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)
})
setTimeout(function() {
console.log(8);
new Promise(function(resolve) {
console.log(9);
resolve();
}).then(function() {
console.log(10)
})
})
console.log(11)
瀏覽器環(huán)境
第一輪事件循環(huán)
- 第一輪事件循環(huán)宏任務(wù)
- 開(kāi)始執(zhí)行代碼跟继,遇到
console.log
輸出 0 - 接著遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 1 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 接著遇到
setTimeout
其回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“宏任務(wù)隊(duì)列” - 接著遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 6 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 接著遇到
setTimeout
其回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“宏任務(wù)隊(duì)列” - 最后遇到
console.log
輸出 11 - 此時(shí)第一輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 0 1 6 11
- 第一輪事件循環(huán)微任務(wù)
- 執(zhí)行注冊(cè)在“微任務(wù)隊(duì)列”里的微任務(wù)肢础,遇到
then
執(zhí)行其回調(diào)輸出 2 - 遇到
then
執(zhí)行其回調(diào)輸出 7 - 此時(shí)第一輪事件循環(huán)微任務(wù)結(jié)束还栓,依次輸出 2 7
- 第一輪事件循環(huán)結(jié)束,此時(shí)“微任務(wù)隊(duì)列”已被清空传轰,“宏任務(wù)隊(duì)列”里有兩個(gè)
setTimeout
的回調(diào)函數(shù),第一個(gè)setTimeout
被送入主線(xiàn)程執(zhí)行棧
第二輪事件循環(huán)
- 第二輪事件循環(huán)宏任務(wù)
- 開(kāi)始執(zhí)行第一個(gè)
setTimeout
回調(diào)函數(shù)- 遇到
console.log
輸出 3 - 遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 4 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 遇到
- 此時(shí)第二輪事件循環(huán)宏任務(wù)結(jié)束谷婆,依次輸出 3 4
- 第二輪事件循環(huán)微任務(wù)
- 執(zhí)行注冊(cè)在“微任務(wù)隊(duì)列”里的微任務(wù)慨蛙,遇到
then
執(zhí)行其回調(diào)輸出 5 - 此時(shí)第二輪事件循環(huán)微任務(wù)結(jié)束辽聊,輸出 5
- 第二輪事件循環(huán)結(jié)束,此時(shí)“微任務(wù)隊(duì)列”已被清空期贫,“宏任務(wù)隊(duì)列”里剩下一個(gè)
setTimeout
的回調(diào)函數(shù)跟匆,其被送入主線(xiàn)程執(zhí)行棧
第三輪事件循環(huán)
- 第三輪事件循環(huán)宏任務(wù)
- 開(kāi)始執(zhí)行第二個(gè)
setTimeout
回調(diào)函數(shù)- 遇到
console.log
輸出 8 - 遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 9 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 遇到
- 此時(shí)第三輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 8 9
- 第三輪事件循環(huán)微任務(wù)
- 執(zhí)行注冊(cè)在“微任務(wù)隊(duì)列”里的微任務(wù)通砍,遇到
then
執(zhí)行其回調(diào)輸出 10 - 此時(shí)第三輪事件循環(huán)微任務(wù)結(jié)束玛臂,輸出 10
- 第三輪事件循環(huán)結(jié)束,此時(shí)“微任務(wù)隊(duì)列”已被清空封孙,“宏任務(wù)隊(duì)列”已被清空
至此整段代碼執(zhí)行完畢迹冤,完整輸出結(jié)果為:0 1 6 11 2 7 3 4 5 8 9 10
Node.JS 環(huán)境
Node.JS
環(huán)境下任務(wù)隊(duì)列有層級(jí)之分,按層級(jí)執(zhí)行任務(wù)隊(duì)列
第一輪事件循環(huán)
- 第一輪事件循環(huán)宏任務(wù)
- 開(kāi)始執(zhí)行代碼虎忌,遇到
console.log
輸出 0 - 接著遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 1 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 接著遇到
setTimeout
其回調(diào)函數(shù)注冊(cè)到 timer 階段 - 接著遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 6 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 接著遇到
setTimeout
其回調(diào)函數(shù)注冊(cè)到 timer 階段 - 最后遇到
console.log
輸出 11 - 此時(shí)第一輪事件循環(huán)宏任務(wù)結(jié)束泡徙,依次輸出 0 1 6 11
- 第一輪事件循環(huán)微任務(wù)
- 執(zhí)行注冊(cè)在“微任務(wù)隊(duì)列”里的微任務(wù),遇到
then
執(zhí)行其回調(diào)輸出 2 - 遇到
then
執(zhí)行其回調(diào)輸出 7 - 此時(shí)第一輪事件循環(huán)微任務(wù)結(jié)束膜蠢,依次輸出 2 7
- 第一輪事件循環(huán)結(jié)束堪藐,此時(shí)“微任務(wù)隊(duì)列”已被清空,
timer
隊(duì)列里有兩個(gè)setTimeout
的回調(diào)函數(shù)
第二輪事件循環(huán)
- 第二輪事件循環(huán) timer 階段
- 執(zhí)行第一個(gè)
setTimeout
回調(diào)函數(shù)- 遇到
console.log
輸出 3 - 遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 4 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 遇到
- 執(zhí)行第二個(gè)
setTimeout
回調(diào)函數(shù)- 遇到
console.log
輸出 8 - 遇到
new Promise
其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行- 遇到
console.log
輸出 9 - 遇到
then
回調(diào)函數(shù)作為異步任務(wù)進(jìn)入“微任務(wù)隊(duì)列”
- 遇到
- 遇到
- 此時(shí)第二輪事件循 timer 階段結(jié)束挑围,依次輸出 3 4 8 9
- 第二輪事件循環(huán) timer 階段微任務(wù)
- 執(zhí)行注冊(cè)在“微任務(wù)隊(duì)列”里的微任務(wù)
- 遇到
then
執(zhí)行其回調(diào)輸出 5 - 遇到
then
執(zhí)行其回調(diào)輸出 10 - 此時(shí)第二輪事件循環(huán) timer 階段微任務(wù)結(jié)束礁竞,輸出 5 10
- 第二輪事件循環(huán)結(jié)束,至此整段代碼執(zhí)行完畢杉辙,完整輸出結(jié)果為:0 1 6 11 2 7 3 4 8 9 5 10