scheduler
模塊用于管理重繪完成后回調(diào)的執(zhí)行邏輯。從輸出分析,對(duì)整個(gè)調(diào)度過(guò)程進(jìn)行梳理绳矩。
基礎(chǔ)前提
瀏覽器渲染與事件循環(huán)
瀏覽器采用多進(jìn)程架構(gòu),包含瀏覽器主進(jìn)程劫侧、渲染進(jìn)程埋酬、插件進(jìn)程哨啃、GPU進(jìn)程。每開(kāi)啟一個(gè)tab時(shí)瀏覽器就會(huì)開(kāi)啟一個(gè)渲染進(jìn)程写妥,該進(jìn)程里包含多個(gè)線程:負(fù)責(zé)運(yùn)行js
拳球、dom
和css
計(jì)算和頁(yè)面渲染的主線程,運(yùn)行worker
的工作線程等珍特。主線程解析html
時(shí)祝峻,遇到script
標(biāo)簽,會(huì)暫停html
的解析扎筒,并開(kāi)始加載莱找、解析并執(zhí)行js
代碼、為了調(diào)度事件嗜桌、用戶交互奥溺、渲染、網(wǎng)絡(luò)請(qǐng)求這些操作骨宠,主線程會(huì)通過(guò)事件循環(huán)來(lái)處理浮定。事件循環(huán)的過(guò)程為:
同步任務(wù)
一個(gè)宏任務(wù)
清空微任務(wù)隊(duì)列
判斷是否渲染視圖(是否有重排、重繪层亿、渲染間隔是否達(dá)到
16.7ms
等)桦卒,為真則渲染視圖,否則跳至步驟1匿又,頁(yè)面渲染前調(diào)用requestAnimationFrame
回調(diào)函數(shù)方灾,最后判斷是否啟動(dòng)空閑時(shí)間算法,如果啟動(dòng)就調(diào)用requestIdleCallback
常見(jiàn)的宏任務(wù):事件回調(diào)碌更、xhr
回調(diào)裕偿、定時(shí)器、I/O
针贬、MessageChannel
常見(jiàn)的微任務(wù):Promise
击费、Generator
拢蛋、Async/Await
桦他、MutationObserver
時(shí)間片
js
在瀏覽器中的執(zhí)行是單線程的,長(zhǎng)時(shí)間的js
任務(wù)執(zhí)行可能會(huì)阻塞其他瀏覽器任務(wù)谆棱,如頁(yè)面渲染快压、用戶交互等,有可能會(huì)造成用戶的卡頓感垃瞧。schedule
中采用時(shí)間分片的策略蔫劣,將任務(wù)細(xì)化為不同的優(yōu)先級(jí),利用瀏覽器的空閑時(shí)間進(jìn)行任務(wù)的執(zhí)行保證UI
操作的流暢个从。瀏覽器的調(diào)度API
主要分為兩種脉幢,高優(yōu)先級(jí)的requestAnimationFrame
與低優(yōu)先級(jí)的requestIdleCallback
歪沃。
將js
任務(wù)分解到時(shí)間片中執(zhí)行后,一次時(shí)間循環(huán)最多只執(zhí)行一個(gè)時(shí)間片嫌松,若還有未完成的任務(wù)沪曙,將這些任務(wù)放到后面的事件循環(huán)的時(shí)間片中執(zhí)行,保證不會(huì)阻塞其他的瀏覽器任務(wù)萎羔。
requestAnimationFrame
requestAnimationFrame
傳入一個(gè)回調(diào)函數(shù)作為參數(shù)液走,該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行〖窒荩回調(diào)函數(shù)執(zhí)行次數(shù)通常是每秒60次缘眶,在大多數(shù)遵循w3c建議的瀏覽器中,回調(diào)函數(shù)執(zhí)行次數(shù)通常與瀏覽器屏幕刷新次數(shù)相匹配髓废。大多數(shù)瀏覽器中巷懈,當(dāng) requestAnimationFrame
運(yùn)行在后臺(tái)標(biāo)簽頁(yè)或隱藏的iframe
中時(shí),requestAnimationFrame
會(huì)被暫停調(diào)用慌洪。
requestAnimationFrame
函數(shù)接收一個(gè)接收DOMHighResTimeStamp
參數(shù)的callback
函數(shù)作為參數(shù)砸喻,返回一個(gè)requestId
供cancelAnimationFrame
以取消。
requestIdleCallback
瀏覽器每秒一般60幀蒋譬,幀與幀的間隔成為時(shí)間片割岛,長(zhǎng)度為 1000 / 60
約16ms
,如果一幀渲染完成的時(shí)間小于16ms
犯助,這個(gè)時(shí)間片就有空閑時(shí)間癣漆。空閑時(shí)間會(huì)被執(zhí)行requestIdleCallback
的回調(diào)函數(shù)剂买。當(dāng)超過(guò)timeout
時(shí)間還不執(zhí)行callback
時(shí)惠爽,callback
將會(huì)被強(qiáng)制執(zhí)行,造成的后果是瞬哼,阻塞本地渲染婚肆,延長(zhǎng)渲染時(shí)間,造成卡頓坐慰、延遲等较性。
callback
函數(shù)接收IdleDeadline
接口類型的參數(shù),是一個(gè)對(duì)象结胀,包含兩個(gè)屬性
didTimeout
赞咙,布爾值,表示任務(wù)是否超時(shí)timeRemaining
糟港,表示當(dāng)前時(shí)間片剩余的時(shí)間攀操。
requestIndleCallback
會(huì)返回一個(gè)id
,傳入cancelIdleCallback
可結(jié)束對(duì)應(yīng)的回調(diào)秸抚。
使用時(shí)間片的實(shí)現(xiàn):
scheduler
每執(zhí)行一次performWorkUntilDeadline
函數(shù)表示執(zhí)行了一個(gè)時(shí)間片速和,執(zhí)行該函數(shù)前會(huì)通過(guò)MessageChannel
或setTimeout
將該函數(shù)放入宏任務(wù)隊(duì)列歹垫,優(yōu)先使用MessageChannel
。
每執(zhí)行一個(gè)時(shí)間片時(shí)颠放,將時(shí)間片長(zhǎng)度 yieldInterval
和過(guò)期時(shí)間deadline
放入執(zhí)行的任務(wù)中县钥,并通返回值判斷是否存在未執(zhí)行完的任務(wù)(表示時(shí)間片已用完)。若存在為執(zhí)行完的任務(wù)慈迈,則讓任務(wù)在下次事件循環(huán)繼續(xù)執(zhí)行若贮。
export {
ImmediatePriority as unstable_ImmediatePriority, // 1
UserBlockingPriority as unstable_UserBlockingPriority, // 2
NormalPriority as unstable_NormalPriority, // 3
IdlePriority as unstable_IdlePriority, // 5
LowPriority as unstable_LowPriority, // 4
unstable_runWithPriority,
unstable_next,
unstable_scheduleCallback,
unstable_cancelCallback,
unstable_wrapCallback,
unstable_getCurrentPriorityLevel,
shouldYieldToHost as unstable_shouldYield,
unstable_requestPaint,
unstable_continueExecution,
unstable_pauseExecution,
unstable_getFirstCallbackNode,
getCurrentTime as unstable_now,
forceFrameRate as unstable_forceFrameRate,
};
export const unstable_Profiling = enableProfiling
? {
startLoggingProfilingEvents,
stopLoggingProfilingEvents,
}
: null;
輸出函數(shù)分析
任務(wù)優(yōu)先級(jí)
react
內(nèi)對(duì)任務(wù)優(yōu)先級(jí)的定義。Scheduler
中任務(wù)有不同的優(yōu)先級(jí)痒留,每個(gè)優(yōu)先級(jí)有對(duì)應(yīng)的過(guò)期時(shí)間谴麦,在生成任務(wù)時(shí)根據(jù)優(yōu)先級(jí)和創(chuàng)建時(shí)間生成任務(wù)的過(guò)期時(shí)間,任務(wù)過(guò)期后才會(huì)放入taskQueue
執(zhí)行伸头,否則放入timerQueue
等待執(zhí)行匾效。
優(yōu)先級(jí) | 值 | 含義 | 過(guò)期時(shí)間 | 過(guò)期時(shí)間的值 |
---|---|---|---|---|
NoPriority |
0 | 無(wú)優(yōu)先級(jí) | ||
ImmediatePriority |
1 | 最高優(yōu)先級(jí) | IMMEDIATE_PRIORITY_TIMEOUT |
-1 |
UserBlockingPriority |
2 | 用戶阻塞型優(yōu)先級(jí) | USER_BLOCKING_PRIORITY_TIMEOUT |
250 |
NormalPriority |
3 | 普通優(yōu)先級(jí) | NORMAL_PRIORITY_TIMEOUT |
5000 |
LowPriority |
4 | 低優(yōu)先級(jí) | LOW_PRIORITY_TIMEOUT |
10000 |
IdlePriority |
5 | 空閑優(yōu)先級(jí) | IDLE_PRIORITY_TIMEOUT |
maxSigned31BitInt = Math.pow(2, 30) - 1 |
環(huán)境中設(shè)置變量分析
//任務(wù)存儲(chǔ)在小頂堆上
var taskQueue = [] // 任務(wù)隊(duì)列
var timerQueue = []; // 延時(shí)任務(wù)隊(duì)列
var taskIdCounter = 1; // 遞增id計(jì)數(shù)器, 用于維護(hù)插入順序
var isSchedulerPaused = false; // 暫停調(diào)度程序,用于調(diào)試
var currentTask = null; // 當(dāng)前任務(wù)
var currentPriorityLevel = NormalPriority; //3 當(dāng)前執(zhí)行任務(wù)的優(yōu)先級(jí)
var isPerformingWork = false; // 是否正在執(zhí)行任務(wù)恤磷,在執(zhí)行工作時(shí)設(shè)置的, 以防止重新進(jìn)入
var isHostCallbackScheduled = false; // 是否有主任務(wù)正在執(zhí)行面哼,是否調(diào)度了 taskQueue, isHostCallbackScheduled為true后才把時(shí)間片放到宏任務(wù)隊(duì)列,之后開(kāi)始執(zhí)行任務(wù)
var isHostTimeoutScheduled = false; // 是否有延時(shí)任務(wù)正在執(zhí)行扫步,是否調(diào)度了 timerQueue, 設(shè)置了 timeout回調(diào)
SchedulerHostConfig
let requestHostCallback; // 請(qǐng)求回調(diào)
let cancelHostCallback; // 取消回調(diào)
let requestHostTimeout; // 請(qǐng)求超時(shí)
let cancelHostTimeout; // 取消超時(shí)
let shouldYieldToHost;
let requestPaint; // 請(qǐng)求繪制
let getCurrentTime; // 獲取當(dāng)前時(shí)間, 優(yōu)先用 performance.now(), 或者用 Date.now() - 初始時(shí)間
let forceFrameRate; // 強(qiáng)制幀率
-
如果
Scheduler
運(yùn)行在非DOM
環(huán)境中魔策,使用setTimeout
回退到一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。環(huán)境中檢測(cè)不到window
或者不支持MessageChannel
時(shí):requestHostCallback
cancelHostCallback
requestHostTimeout
cancelHostTimeout
shouldYieldToHost
requestPaint
forceFrameRate
unstable_runWithPriority
unstable_runWithPriority(*priorityLevel*, *eventHandler*)
主要邏輯:
將
currentPriorityLevel
設(shè)置為priorityLevel
河胎,然后執(zhí)行eventHandler
最后將
currentPriorityLevel
改回之前的值
unstable_next
unstable_next(*eventHandler*)
主要邏輯:
currentPriorityLevel
高于NormalPriority
情況下設(shè)置為NormalPriority
闯袒,否則保持當(dāng)前優(yōu)先級(jí)執(zhí)行
eventHandler
最后將
currentPriorityLevel
改回之前的值
unstable_scheduleCallback
整體流程:
scheduler
中任務(wù)有不同優(yōu)先級(jí),每個(gè)優(yōu)先級(jí)有對(duì)應(yīng)的過(guò)期時(shí)間游岳,在生成任務(wù)根據(jù)優(yōu)先級(jí)和創(chuàng)建事件生成任務(wù)的過(guò)期時(shí)間政敢,任務(wù)過(guò)期后才會(huì)放入taskQueue
執(zhí)行,否則放入timerQueue
等待執(zhí)行TaskQueue
和TimerQueue
是兩個(gè)用小頂堆實(shí)現(xiàn)的具有優(yōu)先級(jí)的任務(wù)隊(duì)列胚迫。TaskQueue
中優(yōu)先級(jí)的索引是expirationTime
喷户,TimerQueue
中優(yōu)先級(jí)的索引使用的是startTime
。SchedulerMinHeap.js
中實(shí)現(xiàn)了對(duì)小頂堆的peek
访锻、pop
褪尝、push
方法。使用advanceTimers
方法可以依據(jù)指定的時(shí)間和任務(wù)的開(kāi)始時(shí)間將TimerQueue
中的任務(wù)更新到TaskQueue
中朗若,更新時(shí)會(huì)同時(shí)更新索引使用的值為expirationTime
通過(guò)
unstable_scheduleCallback
添加任務(wù)恼五,生成一個(gè)任務(wù)對(duì)象昌罩。任務(wù)對(duì)象包含任務(wù)id
哭懈、任務(wù)執(zhí)行函數(shù)、優(yōu)先級(jí)茎用、開(kāi)始時(shí)間遣总、過(guò)期時(shí)間和在隊(duì)列中的順序睬罗。任務(wù)通過(guò)傳入?yún)?shù)中的delay
屬性值來(lái)判斷該任務(wù)是同步任務(wù)還是異步任務(wù)。-
若生成的任務(wù)是同步任務(wù)旭斥,則將該任務(wù)推入
taskQueue
如果當(dāng)前
taskQueue
是未被調(diào)度且任務(wù)未被執(zhí)行容达,則使用requestHostCallback
調(diào)用flushWork
方法requestHostCallback
方法內(nèi)會(huì)觸發(fā)message
事件,performWorkUntilDeadline
函數(shù)作為message
事件的回調(diào)將推入事件循環(huán)的宏任務(wù)隊(duì)列performWorkUntilDeadline
方法會(huì)執(zhí)行一個(gè)時(shí)間片的任務(wù)垂券,時(shí)間片用完后會(huì)判斷是否還有未執(zhí)行的任務(wù)花盐,如果有則再次觸發(fā)message
事件flushWork
先取消timerQueue
的回調(diào),之后設(shè)置isPerformingWork
為false
菇爪,并調(diào)用workLoop
方法執(zhí)行taskQueue
中的任務(wù)workLoop
會(huì)不斷取出taskQueue
中的任務(wù)算芯,直到執(zhí)行完所有的任務(wù)或者執(zhí)行完所有超時(shí)的任務(wù)且時(shí)間片已用完。最后若存在未執(zhí)行完的任務(wù)凳宙,則返回true
熙揍,否則重新設(shè)置timerQueue
中的回調(diào),并返回false
若生成的任務(wù)是異步任務(wù)氏涩,則將任務(wù)推入
timerQueue
届囚。如果當(dāng)前taskQueue
為空且新任務(wù)在timerQueue
中優(yōu)先級(jí)最高,使用requestHostTimeout
調(diào)度handleTimeout
方法handleTimeout
首先判斷當(dāng)前是否在調(diào)度taskQueue
是尖,若沒(méi)有在調(diào)度意系,則判斷taskQueue
是否為空,如果不為空饺汹,則調(diào)度taskQueue
昔字,否則調(diào)度timerQueue
unstable_scheduleCallback(*priorityLevel*, *callback*, *options*)
主要邏輯為,根據(jù)輸入返回newTask
首繁。
- 根據(jù)傳入的
options
更新startTime
作郭,根據(jù)傳入的priorityLevel
更新timeout
,然后計(jì)算expireTime
弦疮,定義newTask
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
-
對(duì)于延時(shí)任務(wù)夹攒,
startTime > currentTime
,startTime
設(shè)置為sortIndex
胁塞,將任務(wù)添加到timerQueue
隊(duì)列咏尝,如果taskQueue
為空且timerQueue
只有newTask
一個(gè)延時(shí)任務(wù)。是否有延時(shí)任務(wù)正在執(zhí)行啸罢,如果有编检,清除定時(shí)器,否則將isHostTimeoutScheduled
設(shè)置為true
扰才。執(zhí)行requestHostTimeout
允懂,延時(shí)處理handleTimeout
。requestHostTimeout
邏輯:接收callback
和ms
衩匣,經(jīng)過(guò)ms
后執(zhí)行callback
蕾总,將getCurrentTime()
的值傳入.handleTimeout
邏輯:isHostTimeoutScheduled
設(shè)置為false
粥航,執(zhí)行advanceTimers
。如果isHostCallbackScheduled
為false
生百,即沒(méi)有主任務(wù)正在執(zhí)行递雀,設(shè)置isHostCallbackScheduled
為true
,將flushWork
傳遞給requestHostCallback
advanceTimers
邏輯:接收一個(gè)參數(shù)currentTime
蚀浆,檢查timerQueue
中的任務(wù)缀程,將不再延時(shí)的任務(wù)添加到taskQueue
中。將timerQueue
中的堆頂任務(wù)彈出市俊,如果不存在timer.callback
杠输,任務(wù)取消,并且彈出timerQueue
秕衙,如果timer.startTime <= currentTime
蠢甲,任務(wù)彈出timerQueue
,并且添加到taskQueue
中.flushWork
邏輯:接收(hasTimeRemaining, initialTime)
兩個(gè)參數(shù)据忘,isHostCallbackScheduled
設(shè)置為false
鹦牛,如果isHostTimeoutScheduled
,設(shè)置為false
勇吊,取消定時(shí)器曼追,然后執(zhí)行workLoop(hasTimeRemaining, initialTime)
workLoop
邏輯:接收(hasTimeRemaining, initialTime)
兩個(gè)參數(shù),當(dāng)前任務(wù)不為空或任務(wù)不停止的情況下汉规,執(zhí)行循環(huán)礼殊。當(dāng)當(dāng)前任務(wù)還沒(méi)有過(guò)期,但是到了deadline
针史,則跳出循環(huán)晶伦;currentTask
有回調(diào)的情況下,執(zhí)行回調(diào)啄枕,currentTask
等于棧頂元素的情況下婚陪,將任務(wù)從taskQueue
中彈出,執(zhí)行advanceTimers
將不再延時(shí)的任務(wù)添加到任務(wù)隊(duì)列频祝;currentTask
沒(méi)有回調(diào)的情況下泌参,將任務(wù)從taskQueue
中彈出。currentTask
為空的情況下常空,獲取timerQueue
的棧頂任務(wù)沽一,放入requestHostTimeout
中。
https://someu.github.io/2020-11-10/react-scheduler%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/
https://juejin.cn/post/6889314677528985614
https://juejin.cn/post/6914089940649246734
md格式的文件直接粘過(guò)來(lái)有點(diǎn)丑漓糙,待補(bǔ)充