一岸蜗、思考
大家都知道異步任務(wù)分為宏任務(wù)和微任務(wù)备蚓,不知道的可以看我的另一篇文章(深入理解Event Loop的運行機制)
要搞清楚requestAnimationFrame和requestIdleCallback是宏任務(wù)還是微任務(wù)就必須要搞清楚下面幾個問題:
- 瀏覽器在每一輪Event Loop事件循環(huán)中都會去渲染屏幕嗎?
- requestAnimationFrame在哪個階段執(zhí)行,是在渲染前還是渲染后?是在微任務(wù)執(zhí)行前還是執(zhí)行后良姆?
- requestIdleCallback在哪個階段執(zhí)行,是在渲染前還是渲染后穴肘? 是在微任務(wù)執(zhí)行前還是執(zhí)行后歇盼?
二、任務(wù)的執(zhí)行時機
在瀏覽器的Event Loop中是有多個任務(wù)隊列的评抚,每個任務(wù)隊列的執(zhí)行時機是不一樣的豹缀,下面直接上干貨,說說瀏覽器執(zhí)行任務(wù)的順序
- 從task任務(wù)隊列中取第一個task(比如setTimeout慨代、setIntervel的回調(diào)邢笙,也可以將同一輪循環(huán)中的所有同步代碼看作是一個宏任務(wù)),執(zhí)行它侍匙。
- 執(zhí)行微任務(wù)隊列里的所有微任務(wù)氮惯。
- 瀏覽器判斷是否更新渲染屏幕,如果需要重新繪制想暗,則執(zhí)行步驟4-13妇汗,如果不需要重新繪制,則流程回到步驟1说莫,這樣不斷循環(huán)杨箭。
- 觸發(fā)resize、scroll事件储狭,建立媒體查詢(執(zhí)行一個任務(wù)中如果生成了微任務(wù)互婿,則執(zhí)行完任務(wù)該后就會執(zhí)行所有的微任務(wù),然后再執(zhí)行下一個任務(wù))辽狈。
- 建立css動畫(執(zhí)行一個任務(wù)中如果生成了微任務(wù)慈参,則執(zhí)行完該任務(wù)后就會執(zhí)行所有的微任務(wù),然后再執(zhí)行下一個任務(wù))刮萌。
- 執(zhí)行requestAnimationFrame回調(diào)(執(zhí)行一個任務(wù)中如果生成了微任務(wù)驮配,則執(zhí)行完該任務(wù)后就會執(zhí)行所有的微任務(wù),然后再執(zhí)行下一個任務(wù))着茸。
- 執(zhí)行 IntersectionObserver 回調(diào)(執(zhí)行一個任務(wù)中如果生成了微任務(wù)僧凤,則執(zhí)行完該任務(wù)后就會執(zhí)行所有的微任務(wù),然后再執(zhí)行下一個任務(wù))元扔。
- 更新渲染屏幕。
- 瀏覽器判斷當(dāng)前幀是否還有空閑時間旋膳,如果有空閑時間澎语,則執(zhí)行步驟10-12。
- 從 requestIdleCallback回調(diào)函數(shù)隊列中取第一個,執(zhí)行它擅羞。
- 執(zhí)行微任務(wù)隊列里的所有微任務(wù)尸变。
- 流程回到步驟9,直到requestIdleCallback回調(diào)函數(shù)隊列清空或當(dāng)前幀沒有空閑時間减俏。
- 流程回到步驟1召烂,這樣不斷循環(huán)。
三娃承、代碼驗證
我們可以寫一些代碼到Chrome瀏覽器中驗證一下奏夫,看看requestAnimationFrame和requestIdleCallback的執(zhí)行順序是怎樣的。
運行以下這段代碼
requestAnimationFrame(()=>{
console.log(111);
setTimeout(() => {
console.log(222);
});
Promise.resolve().then(() => {
console.log(333);
});
})
requestAnimationFrame(() => {
console.log(444);
Promise.resolve().then(() => {
console.log(555);
});
})
輸出結(jié)果運行以下這段代碼
requestIdleCallback(() => {
console.log(111);
setTimeout(() => {
console.log(222);
})
Promise.resolve().then(() => {
console.log(333);
})
})
requestIdleCallback(() => {
console.log(444);
Promise.resolve().then(() => {
console.log(555);
})
})
輸出結(jié)果運行以下這段代碼
Promise.resolve().then(() => {
console.log(111);
setTimeout(() => {
console.log(222);
})
Promise.resolve().then(() => {
console.log(333);
})
})
Promise.resolve().then(() => {
console.log(444);
Promise.resolve().then(() => {
console.log(555);
})
})
輸出結(jié)果四历筝、總結(jié)
- requestAnimationFrame和requestIdleCallback是和宏任務(wù)性質(zhì)一樣的任務(wù)酗昼,只是他們的執(zhí)行時機不同而已。也有人說它們既不是宏任務(wù)也不是微任務(wù)梳猪,其實我們不必糾結(jié)這個麻削,我們所要做的就是知道他們的執(zhí)行時機就好。
- 瀏覽器在每一輪Event Loop事件循環(huán)中不一定會去重新渲染屏幕春弥,會根據(jù)瀏覽器刷新率以及頁面性能或是否后臺運行等因素判斷的呛哟,瀏覽器的每一幀是比較固定的,會盡量保持60Hz的刷新率運行匿沛,每一幀中間可能會進行多輪事件循環(huán)扫责。
- requestAnimationFrame回調(diào)的執(zhí)行與task和microtask無關(guān),而是與瀏覽器是否渲染相關(guān)聯(lián)的俺祠。它是在瀏覽器渲染前公给,在微任務(wù)執(zhí)行后執(zhí)行。
- requestIdleCallback是在瀏覽器渲染后有空閑時間時執(zhí)行蜘渣,如果requestIdleCallback設(shè)置了第二個參數(shù)timeout淌铐,則會在超時后的下一幀強制執(zhí)行。
更多個人文章