scheduler模塊

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拳球、domcss計(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è)requestIdcancelAnimationFrame以取消。

requestIdleCallback

瀏覽器每秒一般60幀蒋譬,幀與幀的間隔成為時(shí)間片割岛,長(zhǎng)度為 1000 / 6016ms,如果一幀渲染完成的時(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ò)MessageChannelsetTimeout將該函數(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í)行

  • TaskQueueTimerQueue是兩個(gè)用小頂堆實(shí)現(xiàn)的具有優(yōu)先級(jí)的任務(wù)隊(duì)列胚迫。TaskQueue中優(yōu)先級(jí)的索引是expirationTime喷户,TimerQueue中優(yōu)先級(jí)的索引使用的是startTimeSchedulerMinHeap.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è)置isPerformingWorkfalse菇爪,并調(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

Untitled Diagram.drawio.png

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 > currentTimestartTime設(shè)置為sortIndex胁塞,將任務(wù)添加到timerQueue隊(duì)列咏尝,如果taskQueue為空且timerQueue只有newTask一個(gè)延時(shí)任務(wù)。是否有延時(shí)任務(wù)正在執(zhí)行啸罢,如果有编检,清除定時(shí)器,否則將isHostTimeoutScheduled設(shè)置為true扰才。執(zhí)行requestHostTimeout允懂,延時(shí)處理handleTimeout

    • requestHostTimeout邏輯:接收callbackms衩匣,經(jīng)過(guò)ms后執(zhí)行callback蕾总,將getCurrentTime()的值傳入.

    • handleTimeout邏輯:isHostTimeoutScheduled設(shè)置為false粥航,執(zhí)行advanceTimers。如果 isHostCallbackScheduledfalse生百,即沒(méi)有主任務(wù)正在執(zhí)行递雀,設(shè)置isHostCallbackScheduledtrue,將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ǔ)充

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铣缠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攘残,老刑警劉巖拙友,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件为狸,死亡現(xiàn)場(chǎng)離奇詭異歼郭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辐棒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門病曾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人漾根,你說(shuō)我怎么就攤上這事泰涂。” “怎么了辐怕?”我有些...
    開(kāi)封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵逼蒙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我寄疏,道長(zhǎng)是牢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任陕截,我火速辦了婚禮驳棱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘农曲。我一直安慰自己社搅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布乳规。 她就那樣靜靜地躺著形葬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暮的。 梳的紋絲不亂的頭發(fā)上荷并,一...
    開(kāi)封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音青扔,去河邊找鬼源织。 笑死,一個(gè)胖子當(dāng)著我的面吹牛微猖,可吹牛的內(nèi)容都是我干的谈息。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼凛剥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼侠仇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逻炊,失蹤者是張志新(化名)和其女友劉穎互亮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體余素,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豹休,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桨吊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片威根。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖视乐,靈堂內(nèi)的尸體忽然破棺而出洛搀,到底是詐尸還是另有隱情,我是刑警寧澤佑淀,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布留美,位于F島的核電站,受9級(jí)特大地震影響伸刃,放射性物質(zhì)發(fā)生泄漏谎砾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一奕枝、第九天 我趴在偏房一處隱蔽的房頂上張望棺榔。 院中可真熱鬧,春花似錦隘道、人聲如沸症歇。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忘晤。三九已至,卻和暖如春激捏,著一層夾襖步出監(jiān)牢的瞬間设塔,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工远舅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闰蛔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓图柏,卻偏偏與公主長(zhǎng)得像序六,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚤吹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容