首先 setTimeout 并沒有特殊,也是一個(gè) task脯厨。另外每次的執(zhí)行過 task 和 大量的 microtask(不一定在一次循環(huán)全執(zhí)行完)后,會進(jìn)行 renderUi 階段,雖然不是每次事件循環(huán)都進(jìn)行 renderUi ,但每次間隔叠聋,也就是傳說中 60hz 的一幀 16ms。
瀏覽器中的事件循環(huán)
宏任務(wù)和微任務(wù)
同步代碼的執(zhí)行也屬于宏任務(wù)
瀏覽器先執(zhí)行同步代碼受裹,遇到宏任務(wù)就放到 macro task 的任務(wù)隊(duì)列中碌补,遇到微任務(wù)就放入 micro task 的隊(duì)列中。在執(zhí)行完同步任務(wù)之后就會先執(zhí)行微任務(wù)隊(duì)列中的所有微任務(wù)棉饶,然后從宏任務(wù)隊(duì)列中取出第一個(gè)宏任務(wù)執(zhí)行厦章,執(zhí)行完畢之后再去執(zhí)行所有的微任務(wù)...這個(gè)過程是循環(huán)不斷的所以被稱為事件循環(huán)。但是需要注意的是瀏覽器會在每 16ms 進(jìn)行一次的 UI 渲染照藻,可能會中斷事件循環(huán)的執(zhí)行
常見的宏任務(wù)
setTimeout袜啃、setInterval、 setImmediate岩梳、script(整體代碼)囊骤、 I/O 操作晃择、UI 渲染
常見的微任務(wù)
MutationObserver Promise
nodejs 中的事件循環(huán)
外部輸入數(shù)據(jù)–>輪詢階段(poll)–>檢查階段(check) setImmediate–>關(guān)閉事件回調(diào)階段(close callback)–>定時(shí)器檢測階段(timer) setTimeout/setInterval–>I/O 事件回調(diào)階段(I/O callbacks) 文件操作的回調(diào)函數(shù)等等–>閑置階段(idle, prepare)–>輪詢階段(按照該順序反復(fù)運(yùn)行)
我們可以接觸到的比較常用的就是 poll,check,timer 這幾個(gè)階段
nodejs中時(shí)間循環(huán)和瀏覽器中的區(qū)別
在 node11 版本之后冀值,對于 macro task 和 micro task 的執(zhí)行時(shí)機(jī)和瀏覽器中相同。
在 node11 之前宫屠,nodejs 和瀏覽器中的事件循環(huán)的區(qū)別就是 micro task 的執(zhí)行時(shí)機(jī)列疗;nodejs中 timers 階段有幾個(gè) setTimeout/setInterval 都會依次執(zhí)行,執(zhí)行完畢所有的 timers 代碼之后才會去執(zhí)行 micro task浪蹂,并不像瀏覽器端抵栈,每執(zhí)行一個(gè)宏任務(wù)后就去執(zhí)行所有微任務(wù)告材。
所以下面的代碼,在 node11 之前的執(zhí)行結(jié)果是和瀏覽器中不相同的
console.log('start')
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
Promise.resolve().then(function() {
console.log('promise3')
})
console.log('end')
//start=>end=>promise3=>timer1=>timer2=>promise1=>promise2
// 瀏覽器和新版本node start=>end=>promise3=>timer1=>promise1=>timer2=>promise2
poll階段 該階段會執(zhí)行所有的回調(diào)
poll 是一個(gè)至關(guān)重要的階段古劲,這一階段中斥赋,系統(tǒng)會做兩件事情
回到 timer 階段執(zhí)行回調(diào)
執(zhí)行 I/O 回調(diào)
沒有設(shè)置timer或者是設(shè)置的timer沒有到時(shí)間
會發(fā)生以下兩件事情
如果 poll 隊(duì)列不為空,會遍歷回調(diào)隊(duì)列并同步執(zhí)行产艾,直到隊(duì)列為空或者達(dá)到系統(tǒng)限制
如果 poll 隊(duì)列為空時(shí)疤剑,會有兩件事發(fā)生
如果有 setImmediate 回調(diào)需要執(zhí)行,poll 階段會停止并且進(jìn)入到 check 階段執(zhí)行回調(diào)
如果沒有 setImmediate 回調(diào)需要執(zhí)行闷堡,會等待回調(diào)被加入到隊(duì)列中并立即執(zhí)行回調(diào)隘膘,這里同樣會有個(gè)超時(shí)時(shí)間設(shè)置防止一直等待下去
設(shè)置了timer且timer時(shí)間已經(jīng)到了
當(dāng)然設(shè)定了 timer 的話且 poll 隊(duì)列為空,則會判斷是否有 timer 超時(shí)杠览,如果有的話會回到 timer 階段執(zhí)行回調(diào)弯菊。
注意點(diǎn)
(1) setTimeout 和 setImmediate
二者非常相似,區(qū)別主要在于調(diào)用時(shí)機(jī)不同踱阿。
setImmediate 設(shè)計(jì)在 poll 階段完成時(shí)執(zhí)行管钳,即 check 階段;
setTimeout 設(shè)計(jì)在 poll 階段為空閑時(shí)扫茅,且設(shè)定時(shí)間到達(dá)后執(zhí)行蹋嵌,但它在 timer 階段執(zhí)行
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
對于以上代碼來說,setTimeout 可能執(zhí)行在前葫隙,也可能執(zhí)行在后栽烂。
首先 setTimeout(fn, 0) === setTimeout(fn, 1),這是由源碼決定的
我們發(fā)現(xiàn)雖然我們傳的超時(shí)時(shí)間是 0恋脚,但是 0 不是合法值腺办,nodejs 會把超時(shí)時(shí)間變成 1。
進(jìn)入事件循環(huán)也是需要成本的糟描,如果在準(zhǔn)備時(shí)候花費(fèi)了大于 1ms 的時(shí)間怀喉,那么在 timer 階段就會直接執(zhí)行 setTimeout 回調(diào)
如果準(zhǔn)備時(shí)間花費(fèi)小于 1ms,那么就是 setImmediate 回調(diào)先執(zhí)行了
但當(dāng)二者在異步 i/o callback 內(nèi)部調(diào)用時(shí)船响,總是先執(zhí)行 setImmediate躬拢,再執(zhí)行 setTimeout
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
// immediate
// timeout
在上述代碼中,setImmediate 永遠(yuǎn)先執(zhí)行见间。因?yàn)閮蓚€(gè)代碼寫在 IO 回調(diào)中聊闯,IO 回調(diào)是在 poll 階段執(zhí)行,當(dāng)回調(diào)執(zhí)行完畢后隊(duì)列為空米诉,發(fā)現(xiàn)存在 setImmediate 回調(diào)菱蔬,所以就直接跳轉(zhuǎn)到 check 階段去執(zhí)行回調(diào)了。
(2) process.nextTick
這個(gè)函數(shù)其實(shí)是獨(dú)立于 Event Loop 之外的,它有一個(gè)自己的隊(duì)列拴泌,當(dāng)每個(gè)階段完成后魏身,如果存在 nextTick 隊(duì)列,就會清空隊(duì)列中的所有回調(diào)函數(shù)蚪腐,并且優(yōu)先于其他 microtask 執(zhí)行箭昵。
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
})
})
})
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1