1.前置知識
node.js
是一個(gè)可以運(yùn)行js的平臺
包括fs模塊藕甩、http模塊以及JS引擎(v8)js引擎(單線程)
同一時(shí)間只做一件事情,但是他也可以同一時(shí)間做多件事請风瘦,因?yàn)樗行值埽ㄆ渌€程)幫他做
比如:
對于一個(gè)ajax請求get('/1.json')
凹蜈,若果這個(gè)請求需要100ms,那么我在這100ms內(nèi)觸發(fā)一個(gè)點(diǎn)擊事件讓它打印出一個(gè)1可以嗎糕伐?
答:可以砰琢,雖然說js是單線程,但是它可以主要是因?yàn)樗恼埱笞屗男值芫W(wǎng)絡(luò)模塊去做了良瞧,這時(shí)候js就處于空閑狀態(tài)陪汽,等到網(wǎng)絡(luò)模塊請求完成就會通知js,js然后就會去執(zhí)行一個(gè)回調(diào)
2.nodejs Event loop
2.1 當(dāng)nodejs啟動時(shí)褥蚯,會執(zhí)行三件事情
- 初始化event loop
- 開始執(zhí)行腳本
- 進(jìn)入event loop
2.2. 處理event loop經(jīng)歷的幾個(gè)階段(初始化的時(shí)候不會有這幾個(gè)階段)
上面的幾個(gè)階段中我們只需要了解紅框中的三個(gè)就可以了挚冤,并且每一個(gè)階段都有自己的一個(gè)隊(duì)列
- timers階段
處理setTimeout和setInterval中到時(shí)的回調(diào)函數(shù),對于timers中隊(duì)列的處理赞庶,setTimeout或setInterval中的函數(shù)都添加到隊(duì)列里训挡,同時(shí)記下來這些函數(shù)什么時(shí)間被調(diào)用到了調(diào)用時(shí)間就調(diào)用,沒到時(shí)間就進(jìn)入下一階段歧强,然后會停留在poll階段- poll階段
輪詢階段
比如上面說的讓兄弟(其他線程處理的請求任務(wù))澜薄,在這個(gè)階段會一直去問執(zhí)行相關(guān)任務(wù)的模塊任務(wù)執(zhí)行完了沒有,一旦任務(wù)執(zhí)行完成摊册,相關(guān)的數(shù)據(jù)就會放到一個(gè)回調(diào)里面然后加入到poll的隊(duì)列中肤京,也就是除了timers階段外的所有回調(diào)都是在poll這個(gè)階段處理的。poll階段會一直重復(fù)檢查剛才timers里沒有到時(shí)間的計(jì)時(shí)器有沒有到時(shí)間丧靡,如果到時(shí)間了蟆沫,就直接通過check到達(dá)timers階段通知timers執(zhí)行這個(gè)回調(diào),然后從timers隊(duì)列中清除
check階段
只處理setImmediate
2.3. 通過一個(gè)例子更加清楚的理解timers是怎么執(zhí)行的
比如一開始有兩個(gè)計(jì)時(shí)器一個(gè)4ms一個(gè)100ms
正常情況:當(dāng)?shù)谝淮蝨imers的時(shí)候4ms已經(jīng)到了所以直接執(zhí)行里面的函數(shù)然后從隊(duì)列中清除温治,然后第二個(gè)100ms并沒有到饭庞,就會進(jìn)入poll階段停留,poll階段會一直讀時(shí)間問100ms有沒有到熬荆,如果到了就直接通過check到timers然后通知它100ms到了執(zhí)行相關(guān)的回調(diào)舟山,從隊(duì)列中清除
特例:因?yàn)閜oll從4ms一直等到100ms所以這段空閑的時(shí)間如果有另一個(gè)請求執(zhí)行完了比如說是在90ms執(zhí)行完了,那么這個(gè)時(shí)候它會去執(zhí)行這個(gè)回調(diào)卤恳,但是這個(gè)函數(shù)調(diào)用也是需要時(shí)間的如果說調(diào)用用了20ms那么也就是說會在110ms的時(shí)候處理完這個(gè)函數(shù)的調(diào)用累盗,也就是會錯(cuò)過100ms這個(gè)時(shí)間點(diǎn),那么就會在110ms的時(shí)候通過check通知timers執(zhí)行100ms那個(gè)回調(diào)
2.4. 關(guān)于setTImeout和setImmediate的執(zhí)行順序
答:正常情況下是setImmediate先執(zhí)行突琳,只有第一次啟動nodejs的情況下timers里的定時(shí)器到時(shí)間了才可能setTimeout先執(zhí)行若债。
原因:因?yàn)閚odejs啟動會執(zhí)行三件事,開始執(zhí)行腳本也就是執(zhí)行你頁面的代碼拆融,這時(shí)候會執(zhí)行你的setTImeout蠢琳,然后才會去處理event loop啊终,而從執(zhí)行腳本到處理event loop中間也需要時(shí)間,setTimeout最小的時(shí)間是4ms傲须,如果從執(zhí)行腳本到處理event loop花了5ms時(shí)間蓝牲,那么一進(jìn)入timers就會發(fā)現(xiàn)時(shí)間到了就會立刻處理回調(diào),所以這時(shí)候setTimeout就會先執(zhí)行
nextTick
進(jìn)入每個(gè)階段前都會執(zhí)行泰讽,包括nodejs啟動的時(shí)候也會執(zhí)行
setTimeout(()=>{
console.log('timeout')
})
setImmediate(()=>{
console.log('immediate')
})
process.nextTick(()=>{
console.log('next tick')
})
//next tick
//timeout
//immediate
Macro Task(宏任務(wù))&Micro Task(微任務(wù))
媽Ma 咪Mi
先執(zhí)行完一個(gè)宏任務(wù)再去執(zhí)行一個(gè)微任務(wù)例衍,微任務(wù)每次都要清空
setTimeout后面的回調(diào)還有當(dāng)前的腳本代碼都是宏任務(wù)
nextTick和Promise都屬于微任務(wù)
上圖中setTimeout里面的回調(diào)是宏任務(wù)所以會放到宏任務(wù)的下一個(gè)任務(wù)隊(duì)列中,而執(zhí)行完一個(gè)宏任務(wù)后緊接著要執(zhí)行一個(gè)微任務(wù)promise里面的是微任務(wù)已卸,所以接著執(zhí)行的是2佛玄,最后執(zhí)行3
重要題目:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
let promise = new Promise((resolve, reject)=>{
console.log(1)
resolve()
})
promise.then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
上面代碼執(zhí)行順序是?
答:
script start
1
promise1
promise2
setTimeout
原因:promise里面的代碼時(shí)立即執(zhí)行的所以在script start后緊接著是1累澡,而setTImeout里面的回調(diào)是宏任務(wù)翎嫡,當(dāng)前代碼就是宏任務(wù),所以需要在下個(gè)微任務(wù)后執(zhí)行永乌,promise.then里面的代碼都是微任務(wù),所以會先pormise最后setTimeout