大家好,我卡頌。
Scheduler(排程器)是React
重要的組成部分。
同時,他也是個獨立的包,任何連續、可中斷的流程都可以用Scheduler
來排程,比如:
const work = {count: 100};
function doWork(work) {
work.count--;
console.log('do work!')
}
work
滿足兩個條件:
- 工作是連續的。一共需要執行100次,每次執行時呼叫
doWork
- 工作是可中斷的。中斷恢復後,接著中斷前的
work.count
繼續執行就行
滿足這兩個條件的工作都可以用Scheduler
來排程。
排程後,Scheduler
內部會生成對應task
,並在正確的時機執行task.callback
:
const task1 = {
// 過期時間 等於 當前時間 + 優先順序對應時間
expirationTime: currentTime + priority,
callback: doWork.bind(null, work)
}
本文會講解Scheduler
的實現原理。知道你不喜歡看大段的程式碼,所以本文沒有一行程式碼。文末有Scheduler
的原始碼地址,感興趣的話可以去看看。
開整~
工作流程概覽
Scheduler
的工作原理如下圖,接下來會詳細解釋:
在Scheduler
中有兩個容易混淆的概念:
- delay
delay
代表task需要延遲執行的時間。配置了delay
的task
會先進入timerQueue
中。
當delay
對應時間到期後,該task
會轉移到taskQueue
中。
- expirationTime
expirationTime
代表task的過期時間。
不是所有task
都會配置delay
,沒有配置delay
的task
會直接進入taskQueue
。這就導致taskQueue
中可能存在多個task
。
如何決定哪個task.callback
先執行呢?Scheduler
根據task.expirationTime
作為排序依據,值越小優先順序越高。
如果task.expirationTime
小於當前時間,不僅優先順序最高,而且task.callback
的執行不會被中斷。
總結一下task
的幾種情況:
- 配置
delay
且delay
未到期:task
一定不會執行 - 配置
delay
且到期,或者未配置delay
的task
,同時task.expirationTime
未到期:根據task.expirationTime
排序後,按順序執行 task.expirationTime
到期的task
:優先順序最高,且同步、不可中斷
工作流程詳解
將流程概覽圖替換為Scheduler
中具體方法後,如下:
完整工作流程如下:
- 執行
Scheduler.scheduleCallback
生成task
根據是否傳遞delay引數,生成的task
會進入timerQueue
或taskQueue
。
- 當
timerQueue
中第一個task
延遲的時間到期後,執行advanceTimers
將到期的task從timerQueue
中移到taskQueue
中
其中,timerQueue
、taskQueue
的資料結構為小頂堆
實現的優先順序佇列
。
- 接下來,執行
requestHostCallback
方法,他會在新的巨集任務
中執行workLoop
方法
在巨集任務中執行回撥的方法很多,Scheduler
在瀏覽器環境預設使用MessageChannel
實現。
如果不支援MessageChannel
,會降級到setTimeout
。Node
或老版IE
下會使用setImmediate
。
workLoop
方法會迴圈消費taskQueue
中的task
(即執行task.callback
),直到滿足如下條件之一,中斷迴圈:
taskQueue
中不存在task
- 時間切片用盡
- 迴圈中斷後,如果
taskQueue
不為空,則進入步驟3。如果timerQueue
不為空,則進入步驟2
總結
總結一下,Scheduler
的完整執行流程包括兩個迴圈:
taskQueue
的生產(從timerQueue
中移入或執行scheduleCallback
生成)到消費的過程(即圖中灰色部分),這是個非同步迴圈taskQueue
的具體消費過程(即workLoop
方法的執行),這是個同步迴圈
如果你想了解React中如何使用Scheduler,可以參考100行程式碼實現React核心排程功能
歡迎加入人類高質量前端框架群,帶飛