溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

React的調(diào)度機(jī)制原理是什么

發(fā)布時間:2021-10-19 15:22:07 來源:億速云 閱讀:143 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“React的調(diào)度機(jī)制原理是什么”,在日常操作中,相信很多人在React的調(diào)度機(jī)制原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”React的調(diào)度機(jī)制原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

 點(diǎn)擊進(jìn)入React源碼調(diào)試倉庫。

Scheduler作為一個獨(dú)立的包,可以獨(dú)自承擔(dān)起任務(wù)調(diào)度的職責(zé),你只需要將任務(wù)和任務(wù)的優(yōu)先級交給它,它就可以幫你管理任務(wù),安排任務(wù)的執(zhí)行。這就是React和Scheduler配合工作的模式。

對于多個任務(wù),它會先執(zhí)行優(yōu)先級高的。聚焦到單個任務(wù)的執(zhí)行上,會被Scheduler有節(jié)制地去執(zhí)行。換句話說,線程只有一個,它不會一直占用著線程去執(zhí)行任務(wù)。而是執(zhí)行一會,中斷一下,如此往復(fù)。用這樣的模式,來避免一直占用有限的資源執(zhí)行耗時較長的任務(wù),解決用戶操作時頁面卡頓的問題,實(shí)現(xiàn)更快的響應(yīng)。

我們可以從中梳理出Scheduler中兩個重要的行為:多個任務(wù)的管理、單個任務(wù)的執(zhí)行控制。

基本概念

為了實(shí)現(xiàn)上述的兩個行為,它引入兩個概念:任務(wù)優(yōu)先級 、 時間片。

任務(wù)優(yōu)先級讓任務(wù)按照自身的緊急程度排序,這樣可以讓優(yōu)先級最高的任務(wù)最先被執(zhí)行到。

時間片規(guī)定的是單個任務(wù)在這一幀內(nèi)最大的執(zhí)行時間,任務(wù)一旦執(zhí)行時間超過時間片,則會被打斷,有節(jié)制地執(zhí)行任務(wù)。這樣可以保證頁面不會因?yàn)槿蝿?wù)連續(xù)執(zhí)行的時間過長而產(chǎn)生卡頓。

原理概述

基于任務(wù)優(yōu)先級和時間片的概念,Scheduler圍繞著它的核心目標(biāo) - 任務(wù)調(diào)度,衍生出了兩大核心功能:任務(wù)隊列管理 和 時間片下任務(wù)的中斷和恢復(fù)。

任務(wù)隊列管理

任務(wù)隊列管理對應(yīng)了Scheduler的多任務(wù)管理這一行為。在Scheduler內(nèi)部,把任務(wù)分成了兩種:未過期的和已過期的,分別用兩個隊列存儲,前者存到timerQueue中,后者存到taskQueue中。

如何區(qū)分任務(wù)是否過期?

用任務(wù)的開始時間(startTime)和當(dāng)前時間(currentTime)作比較。開始時間大于當(dāng)前時間,說明未過期,放到timerQueue;開始時間小于等于當(dāng)前時間,說明已過期,放到taskQueue。

不同隊列中的任務(wù)如何排序?

當(dāng)任務(wù)一個個入隊的時候,自然要對它們進(jìn)行排序,保證緊急的任務(wù)排在前面,所以排序的依據(jù)就是任務(wù)的緊急程度。而taskQueue和timerQueue中任務(wù)緊急程度的判定標(biāo)準(zhǔn)是有區(qū)別的。

  •  taskQueue中,依據(jù)任務(wù)的過期時間(expirationTime)排序,過期時間越早,說明越緊急,過期時間小的排在前面。過期時間根據(jù)任務(wù)優(yōu)先級計算得出,優(yōu)先級越高,過期時間越早。

  •  timerQueue中,依據(jù)任務(wù)的開始時間(startTime)排序,開始時間越早,說明會越早開始,開始時間小的排在前面。任務(wù)進(jìn)來的時候,開始時間默認(rèn)是當(dāng)前時間,如果進(jìn)入調(diào)度的時候傳了延遲時間,開始時間則是當(dāng)前時間與延遲時間的和。

任務(wù)入隊兩個隊列,之后呢?

如果放到了taskQueue,那么立即調(diào)度一個函數(shù)去循環(huán)taskQueue,挨個執(zhí)行里面的任務(wù)。

如果放到了timerQueue,那么說明它里面的任務(wù)都不會立即執(zhí)行,那就等到了timerQueue里面排在第一個任務(wù)的開始時間,看這個任務(wù)是否過期,如果是,則把任務(wù)從timerQueue中拿出來放入taskQueue,調(diào)度一個函數(shù)去循環(huán)它,執(zhí)行掉里面的任務(wù);否則過一會繼續(xù)檢查這第一個任務(wù)是否過期。

任務(wù)隊列管理相對于單個任務(wù)的執(zhí)行,是宏觀層面的概念,它利用任務(wù)的優(yōu)先級去管理任務(wù)隊列中的任務(wù)順序,始終讓最緊急的任務(wù)被優(yōu)先處理。

單個任務(wù)的中斷以及恢復(fù)

單個任務(wù)的中斷以及恢復(fù)對應(yīng)了Scheduler的單個任務(wù)執(zhí)行控制這一行為。在循環(huán)taskQueue執(zhí)行每一個任務(wù)時,如果某個任務(wù)執(zhí)行時間過長,達(dá)到了時間片限制的時間,那么該任務(wù)必須中斷,以便于讓位給更重要的事情(如瀏覽器繪制),等事情完成,再恢復(fù)執(zhí)行任務(wù)。

例如這個例子,點(diǎn)擊按鈕渲染140000個DOM節(jié)點(diǎn),為的是讓React通過scheduler調(diào)度一個耗時較長的更新任務(wù)。同時拖動方塊,這是為了模擬用戶交互。更新任務(wù)會占用線程去執(zhí)行任務(wù),用戶交互要也要占用線程去響應(yīng)頁面,這就決定了它們兩個是互斥的關(guān)系。在React的concurrent模式下,通過Scheduler調(diào)度的更新任務(wù)遇到用戶交互之后,會是下面動圖里的效果。

React的調(diào)度機(jī)制原理是什么

執(zhí)行React任務(wù)和頁面響應(yīng)交互這兩件事情是互斥的,但因?yàn)镾cheduler可以利用時間片中斷React任務(wù),然后讓出線程給瀏覽器去繪制,所以一開始在fiber樹的構(gòu)建階段,拖動方塊會得到及時的反饋。但是后面卡了一下,這是因?yàn)閒iber樹構(gòu)建完成,進(jìn)入了同步的commit階段,導(dǎo)致交互卡頓。分析頁面的渲染過程可以非常直觀地看到通過時間片的控制。主線程被讓出去進(jìn)行頁面的繪制(Painting和Rendering,綠色和紫色的部分)。

React的調(diào)度機(jī)制原理是什么

Scheduler要實(shí)現(xiàn)這樣的調(diào)度效果需要兩個角色:任務(wù)的調(diào)度者、任務(wù)的執(zhí)行者。調(diào)度者調(diào)度一個執(zhí)行者,執(zhí)行者去循環(huán)taskQueue,逐個執(zhí)行任務(wù)。當(dāng)某個任務(wù)的執(zhí)行時間比較長,執(zhí)行者會根據(jù)時間片中斷任務(wù)執(zhí)行,然后告訴調(diào)度者:我現(xiàn)在正執(zhí)行的這個任務(wù)被中斷了,還有一部分沒完成,但現(xiàn)在必須讓位給更重要的事情,你再調(diào)度一個執(zhí)行者吧,好讓這個任務(wù)能在之后被繼續(xù)執(zhí)行完(任務(wù)的恢復(fù))。于是,調(diào)度者知道了任務(wù)還沒完成,需要繼續(xù)做,它會再調(diào)度一個執(zhí)行者去繼續(xù)完成這個任務(wù)。

通過執(zhí)行者和調(diào)度者的配合,可以實(shí)現(xiàn)任務(wù)的中斷和恢復(fù)。

原理小結(jié)

Scheduler管理著taskQueue和timerQueue兩個隊列,它會定期將timerQueue中的過期任務(wù)放到taskQueue中,然后讓調(diào)度者通知執(zhí)行者循環(huán)taskQueue執(zhí)行掉每一個任務(wù)。執(zhí)行者控制著每個任務(wù)的執(zhí)行,一旦某個任務(wù)的執(zhí)行時間超出時間片的限制。就會被中斷,然后當(dāng)前的執(zhí)行者退場,退場之前會通知調(diào)度者再去調(diào)度一個新的執(zhí)行者繼續(xù)完成這個任務(wù),新的執(zhí)行者在執(zhí)行任務(wù)時依舊會根據(jù)時間片中斷任務(wù),然后退場,重復(fù)這一過程,直到當(dāng)前這個任務(wù)徹底完成后,將任務(wù)從taskQueue出隊。taskQueue中每一個任務(wù)都被這樣處理,最終完成所有任務(wù),這就是Scheduler的完整工作流程。

這里面有一個關(guān)鍵點(diǎn),就是執(zhí)行者如何知道這個任務(wù)到底完成沒完成呢?這是另一個話題了,也就是判斷任務(wù)的完成狀態(tài)。在講解執(zhí)行者執(zhí)行任務(wù)的細(xì)節(jié)時會重點(diǎn)突出。

以上是Scheduler原理的概述,下面開始是對React和Scheduler聯(lián)合工作機(jī)制的詳細(xì)解讀。涉及React與Scheduler的連接、調(diào)度入口、任務(wù)優(yōu)先級、任務(wù)過期時間、任務(wù)中斷和恢復(fù)、判斷任務(wù)的完成狀態(tài)等內(nèi)容。

詳細(xì)流程

在開始之前,我們先看一下React和Scheduler它們二者構(gòu)成的一個系統(tǒng)的示意圖。

React的調(diào)度機(jī)制原理是什么

整個系統(tǒng)分為三部分:

  •  產(chǎn)生任務(wù)的地方:React

  •  React和Scheduler交流的翻譯者:SchedulerWithReactIntegration

  •  任務(wù)的調(diào)度者:Scheduler

React中通過下面的代碼,讓fiber樹的構(gòu)建任務(wù)進(jìn)入調(diào)度流程:

scheduleCallback(    schedulerPriorityLevel,    performConcurrentWorkOnRoot.bind(null, root),  );

任務(wù)通過翻譯者交給Scheduler,Scheduler進(jìn)行真正的任務(wù)調(diào)度,那么為什么需要一個翻譯者的角色呢?

React與Scheduler的連接

Scheduler幫助React調(diào)度各種任務(wù),但是本質(zhì)上它們是兩個完全不耦合的東西,二者各自都有自己的優(yōu)先級機(jī)制,那么這時就需要有一個中間角色將它們連接起來。

實(shí)際上,在react-reconciler中提供了這樣一個文件專門去做這樣的工作,它就是SchedulerWithReactIntegration.old(new).js。它將二者的優(yōu)先級翻譯了一下,讓React和Scheduler能讀懂對方。另外,封裝了一些Scheduler中的函數(shù)供React使用。

在執(zhí)行React任務(wù)的重要文件ReactFiberWorkLoop.js中,關(guān)于Scheduler的內(nèi)容都是從SchedulerWithReactIntegration.old(new).js導(dǎo)入的。它可以理解成是React和Scheduler之間的橋梁。

// ReactFiberWorkLoop.js  import {    scheduleCallback,    cancelCallback,    getCurrentPriorityLevel,    runWithPriority,    shouldYield,    requestPaint,    now,    NoPriority as NoSchedulerPriority,    ImmediatePriority as ImmediateSchedulerPriority,    UserBlockingPriority as UserBlockingSchedulerPriority,    NormalPriority as NormalSchedulerPriority,    flushSyncCallbackQueue,    scheduleSyncCallback,  } from './SchedulerWithReactIntegration.old';

SchedulerWithReactIntegration.old(new).js通過封裝Scheduler的內(nèi)容,對React提供兩種調(diào)度入口函數(shù):scheduleCallback 和 scheduleSyncCallback。任務(wù)通過調(diào)度入口函數(shù)進(jìn)入調(diào)度流程。

例如,fiber樹的構(gòu)建任務(wù)在concurrent模式下通過scheduleCallback完成調(diào)度,在同步渲染模式下由scheduleSyncCallback完成。

// concurrentMode  // 將本次更新任務(wù)的優(yōu)先級轉(zhuǎn)化為調(diào)度優(yōu)先級  // schedulerPriorityLevel為調(diào)度優(yōu)先級  const schedulerPriorityLevel = lanePriorityToSchedulerPriority(    newCallbackPriority,  );  // concurrent模式  scheduleCallback(    schedulerPriorityLevel,    performConcurrentWorkOnRoot.bind(null, root),  );  // 同步渲染模式  scheduleSyncCallback(    performSyncWorkOnRoot.bind(null, root),  )

它們兩個其實(shí)都是對Scheduler中scheduleCallback的封裝,只不過傳入的優(yōu)先級不同而已,前者是傳遞的是已經(jīng)本次更新的lane計算得出的調(diào)度優(yōu)先級,后者傳遞的是最高級別的優(yōu)先級。另外的區(qū)別是,前者直接將任務(wù)交給Scheduler,而后者先將任務(wù)放到SchedulerWithReactIntegration.old(new).js自己的同步隊列中,再將執(zhí)行同步隊列的函數(shù)交給Scheduler,以最高優(yōu)先級進(jìn)行調(diào)度,由于傳入了最高優(yōu)先級,意味著它將會是立即過期的任務(wù),會立即執(zhí)行掉它,這樣能夠保證在下一次事件循環(huán)中執(zhí)行掉任務(wù)。

function scheduleCallback(    reactPriorityLevel: ReactPriorityLevel,    callback: SchedulerCallback,    options: SchedulerCallbackOptions | void | null,  ) {    // 將react的優(yōu)先級翻譯成Scheduler的優(yōu)先級    const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);    // 調(diào)用Scheduler的scheduleCallback,傳入優(yōu)先級進(jìn)行調(diào)度    return Scheduler_scheduleCallback(priorityLevel, callback, options);  } function scheduleSyncCallback(callback: SchedulerCallback) {    if (syncQueue === null) {      syncQueue = [callback];      // 以最高優(yōu)先級去調(diào)度刷新syncQueue的函數(shù)      immediateQueueCallbackNode = Scheduler_scheduleCallback(        Scheduler_ImmediatePriority,        flushSyncCallbackQueueImpl,      );    } else {      syncQueue.push(callback);    }    return fakeCallbackNode;  }

Scheduler中的優(yōu)先級

說到優(yōu)先級,我們來看一下Scheduler自己的優(yōu)先級級別,它為任務(wù)定義了以下幾種級別的優(yōu)先級:

export const NoPriority = 0; // 沒有任何優(yōu)先級  export const ImmediatePriority = 1; // 立即執(zhí)行的優(yōu)先級,級別最高  export const UserBlockingPriority = 2; // 用戶阻塞級別的優(yōu)先級  export const NormalPriority = 3; // 正常的優(yōu)先級  export const LowPriority = 4; // 較低的優(yōu)先級  export const IdlePriority = 5; // 優(yōu)先級最低,表示任務(wù)可以閑置

任務(wù)優(yōu)先級的作用已經(jīng)提到過,它是計算任務(wù)過期時間的重要依據(jù),事關(guān)過期任務(wù)在taskQueue中的排序。

// 不同優(yōu)先級對應(yīng)的不同的任務(wù)過期時間間隔  var IMMEDIATE_PRIORITY_TIMEOUT = -1;  var USER_BLOCKING_PRIORITY_TIMEOUT = 250;  var NORMAL_PRIORITY_TIMEOUT = 5000;  var LOW_PRIORITY_TIMEOUT = 10000;  // Never times out  var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; ...   // 計算過期時間(scheduleCallback函數(shù)中的內(nèi)容)  var timeout;  switch (priorityLevel) {  case ImmediatePriority:    timeout = IMMEDIATE_PRIORITY_TIMEOUT;    break;  case UserBlockingPriority:    timeout = USER_BLOCKING_PRIORITY_TIMEOUT;    break;  case IdlePriority:    timeout = IDLE_PRIORITY_TIMEOUT;    break;  case LowPriority:    timeout = LOW_PRIORITY_TIMEOUT;    break;  case NormalPriority:  default:    timeout = NORMAL_PRIORITY_TIMEOUT;    break;  }  // startTime可暫且認(rèn)為是當(dāng)前時間  var expirationTime = startTime + timeout;

可見,過期時間是任務(wù)開始時間加上timeout,而這個timeout則是通過任務(wù)優(yōu)先級計算得出。

React中更全面的優(yōu)先級講解在我寫的這一篇文章中:React中的優(yōu)先級

調(diào)度入口 - scheduleCallback

通過上面的梳理,我們知道Scheduler中的scheduleCallback是調(diào)度流程開始的關(guān)鍵點(diǎn)。在進(jìn)入這個調(diào)度入口之前,我們先來認(rèn)識一下Scheduler中的任務(wù)是什么形式:

var newTask = {      id: taskIdCounter++,      // 任務(wù)函數(shù)      callback,      // 任務(wù)優(yōu)先級      priorityLevel,      // 任務(wù)開始的時間      startTime,      // 任務(wù)的過期時間      expirationTime,      // 在小頂堆隊列中排序的依據(jù)      sortIndex: -1,    };
  •  callback:真正的任務(wù)函數(shù),重點(diǎn),也就是外部傳入的任務(wù)函數(shù),例如構(gòu)建fiber樹的任務(wù)函數(shù):performConcurrentWorkOnRoot

  •  priorityLevel:任務(wù)優(yōu)先級,參與計算任務(wù)過期時間

  •  startTime:表示任務(wù)開始的時間,影響它在timerQueue中的排序

  •  expirationTime:表示任務(wù)何時過期,影響它在taskQueue中的排序

  •  sortIndex:在小頂堆隊列中排序的依據(jù),在區(qū)分好任務(wù)是過期或非過期之后,sortIndex會被賦值為expirationTime或startTime,為兩個小頂堆的隊列(taskQueue,timerQueue)提供排序依據(jù)

真正的重點(diǎn)是callback,作為任務(wù)函數(shù),它的執(zhí)行結(jié)果會影響到任務(wù)完成狀態(tài)的判斷,后面我們會講到,暫時先無需關(guān)注。現(xiàn)在我們先來看看scheduleCallback做的事情:它負(fù)責(zé)生成調(diào)度任務(wù)、根據(jù)任務(wù)是否過期將任務(wù)放入timerQueue或taskQueue,然后觸發(fā)調(diào)度行為,讓任務(wù)進(jìn)入調(diào)度。完整代碼如下:

function unstable_scheduleCallback(priorityLevel, callback, options) {    // 獲取當(dāng)前時間,它是計算任務(wù)開始時間、過期時間和判斷任務(wù)是否過期的依據(jù)    var currentTime = getCurrentTime();    // 確定任務(wù)開始時間    var startTime;    // 從options中嘗試獲取delay,也就是推遲時間    if (typeof options === 'object' && options !== null) {      var delay = options.delay;      if (typeof delay === 'number' && delay > 0) {        // 如果有delay,那么任務(wù)開始時間就是當(dāng)前時間加上delay        startTime = currentTime + delay;      } else {        // 沒有delay,任務(wù)開始時間就是當(dāng)前時間,也就是任務(wù)需要立刻開始        startTime = currentTime;      }    } else {      startTime = currentTime;    }    // 計算timeout    var timeout;    switch (priorityLevel) {      case ImmediatePriority:        timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1        break;      case UserBlockingPriority:        timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250        break;      case IdlePriority:        timeout = IDLE_PRIORITY_TIMEOUT; // 1073741823 ms        break;      case LowPriority:        timeout = LOW_PRIORITY_TIMEOUT; // 10000        break;      case NormalPriority:      default:        timeout = NORMAL_PRIORITY_TIMEOUT; // 5000        break;    }    // 計算任務(wù)的過期時間,任務(wù)開始時間 + timeout    // 若是立即執(zhí)行的優(yōu)先級(ImmediatePriority),    // 它的過期時間是startTime - 1,意味著立刻就過期    var expirationTime = startTime + timeout;    // 創(chuàng)建調(diào)度任務(wù)    var newTask = {      id: taskIdCounter++,      // 真正的任務(wù)函數(shù),重點(diǎn)      callback,      // 任務(wù)優(yōu)先級      priorityLevel,      // 任務(wù)開始的時間,表示任務(wù)何時才能執(zhí)行      startTime,      // 任務(wù)的過期時間      expirationTime,      // 在小頂堆隊列中排序的依據(jù)      sortIndex: -1,    };    // 下面的if...else判斷各自分支的含義是:    // 如果任務(wù)未過期,則將 newTask 放入timerQueue, 調(diào)用requestHostTimeout,    // 目的是在timerQueue中排在最前面的任務(wù)的開始時間的時間點(diǎn)檢查任務(wù)是否過期,    // 過期則立刻將任務(wù)加入taskQueue,開始調(diào)度   // 如果任務(wù)已過期,則將 newTask 放入taskQueue,調(diào)用requestHostCallback,    // 開始調(diào)度執(zhí)行taskQueue中的任務(wù)    if (startTime > currentTime) {      // 任務(wù)未過期,以開始時間作為timerQueue排序的依據(jù)      newTask.sortIndex = startTime;      push(timerQueue, newTask);      if (peek(taskQueue) === null && newTask === peek(timerQueue)) {       // 如果現(xiàn)在taskQueue中沒有任務(wù),并且當(dāng)前的任務(wù)是timerQueue中排名最靠前的那一個        // 那么需要檢查timerQueue中有沒有需要放到taskQueue中的任務(wù),這一步通過調(diào)用        // requestHostTimeout實(shí)現(xiàn)        if (isHostTimeoutScheduled) {          // 因?yàn)榧磳⒄{(diào)度一個requestHostTimeout,所以如果之前已經(jīng)調(diào)度了,那么取消掉          cancelHostTimeout();        } else {          isHostTimeoutScheduled = true;        }        // 調(diào)用requestHostTimeout實(shí)現(xiàn)任務(wù)的轉(zhuǎn)移,開啟調(diào)度        requestHostTimeout(handleTimeout, startTime - currentTime);      }    } else {      // 任務(wù)已經(jīng)過期,以過期時間作為taskQueue排序的依據(jù)      newTask.sortIndex = expirationTime;      push(taskQueue, newTask);      // 開始執(zhí)行任務(wù),使用flushWork去執(zhí)行taskQueue      if (!isHostCallbackScheduled && !isPerformingWork) {        isHostCallbackScheduled = true;        requestHostCallback(flushWork);      }    }    return newTask;  }

這個過程中的重點(diǎn)是任務(wù)過期與否的處理。

針對未過期任務(wù),會放入timerQueue,并按照開始時間排列,然后調(diào)用requestHostTimeout,為的是等一會,等到了timerQueue中那個應(yīng)該最早開始的任務(wù)(排在第一個的任務(wù))的開始時間,再去檢查它是否過期,如果它過期則放到taskQueue中,這樣任務(wù)就可以被執(zhí)行了,否則繼續(xù)等。這個過程通過handleTimeout完成。

handleTimeout的職責(zé)是:

  •  調(diào)用advanceTimers,檢查timerQueue隊列中過期的任務(wù),放到taskQueue中。

  •  檢查是否已經(jīng)開始調(diào)度,如尚未調(diào)度,檢查taskQueue中是否已經(jīng)有任務(wù):

    •   如果有,而且現(xiàn)在是空閑的,說明之前的advanceTimers已經(jīng)將過期任務(wù)放到了taskQueue,那么現(xiàn)在立即開始調(diào)度,執(zhí)行任務(wù)

    •   如果沒有,而且現(xiàn)在是空閑的,說明之前的advanceTimers并沒有檢查到timerQueue中有過期任務(wù),那么再次調(diào)用requestHostTimeout重復(fù)這一過程。

總之,要把timerQueue中的任務(wù)全部都轉(zhuǎn)移到taskQueue中執(zhí)行掉才行。

針對已過期任務(wù),在將它放入taskQueue之后,調(diào)用requestHostCallback,讓調(diào)度者調(diào)度一個執(zhí)行者去執(zhí)行任務(wù),也就意味著調(diào)度流程開始。

開始調(diào)度-找出調(diào)度者和執(zhí)行者

Scheduler通過調(diào)用requestHostCallback讓任務(wù)進(jìn)入調(diào)度流程,回顧上面scheduleCallback最終調(diào)用requestHostCallback執(zhí)行任務(wù)的地方:

if (!isHostCallbackScheduled && !isPerformingWork) {    isHostCallbackScheduled = true;    // 開始進(jìn)行調(diào)度    requestHostCallback(flushWork);  }

它既然把flushWork作為入?yún)?,那么任?wù)的執(zhí)行者本質(zhì)上調(diào)用的就是flushWork,我們先不管執(zhí)行者是如何執(zhí)行任務(wù)的,先關(guān)注它是如何被調(diào)度的,需要先找出調(diào)度者,這需要看一下requestHostCallback的實(shí)現(xiàn):

Scheduler區(qū)分了瀏覽器環(huán)境和非瀏覽器環(huán)境,為requestHostCallback做了兩套不同的實(shí)現(xiàn)。在非瀏覽器環(huán)境下,使用setTimeout實(shí)現(xiàn).

requestHostCallback = function(cb) {     if (_callback !== null) {       setTimeout(requestHostCallback, 0, cb);     } else {       _callback = cb;       setTimeout(_flushCallback, 0);     }   };

在瀏覽器環(huán)境,用MessageChannel實(shí)現(xiàn),關(guān)于MessageChannel的介紹就不再贅述。

const channel = new MessageChannel();    const port = channel.port2;    channel.port1.onmessage = performWorkUntilDeadline;    requestHostCallback = function(callback) {      scheduledHostCallback = callback;      if (!isMessageLoopRunning) {        isMessageLoopRunning = true;        port.postMessage(null);      }    };

之所以有兩種實(shí)現(xiàn),是因?yàn)榉菫g覽器環(huán)境不存在屏幕刷新率,沒有幀的概念,也就不會有時間片,這與在瀏覽器環(huán)境下執(zhí)行任務(wù)有本質(zhì)區(qū)別,因?yàn)榉菫g覽器環(huán)境基本不胡有用戶交互,所以該場景下不判斷任務(wù)執(zhí)行時間是否超出了時間片限制,而瀏覽器環(huán)境任務(wù)的執(zhí)行會有時間片的限制。除了這一點(diǎn)之外,雖然兩種環(huán)境下實(shí)現(xiàn)方式不一樣,但是做的事情大致相同。

先看非瀏覽器環(huán)境,它將入?yún)ⅲ▓?zhí)行任務(wù)的函數(shù))存儲到內(nèi)部的變量_callback上,然后調(diào)度_flushCallback去執(zhí)行這個此變量_callback,taskQueue被清空。

再看瀏覽器環(huán)境,它將入?yún)ⅲ▓?zhí)行任務(wù)的函數(shù))存到內(nèi)部的變量scheduledHostCallback上,然后通過MessageChannel的port去發(fā)送一個消息,讓channel.port1的監(jiān)聽函數(shù)performWorkUntilDeadline得以執(zhí)行。performWorkUntilDeadline內(nèi)部會執(zhí)行掉scheduledHostCallback,最后taskQueue被清空。

通過上面的描述,可以很清楚得找出調(diào)度者:非瀏覽器環(huán)境是setTimeout,瀏覽器環(huán)境是port.postMessage。而兩個環(huán)境的執(zhí)行者也顯而易見,前者是_flushCallback,后者是performWorkUntilDeadline,執(zhí)行者做的事情都是去調(diào)用實(shí)際的任務(wù)執(zhí)行函數(shù)。

因?yàn)楸疚膰@Scheduler的時間片調(diào)度行為展開,所以主要探討瀏覽器環(huán)境下的調(diào)度行為,performWorkUntilDeadline涉及到調(diào)用任務(wù)執(zhí)行函數(shù)去執(zhí)行任務(wù),這個過程中會涉及任務(wù)的中斷和恢復(fù)、任務(wù)完成狀態(tài)的判斷,接下來的內(nèi)容將重點(diǎn)對這兩點(diǎn)進(jìn)行講解。

任務(wù)執(zhí)行 - 從performWorkUntilDeadline說起

在文章開頭的原理概述中提到過performWorkUntilDeadline作為執(zhí)行者,它的作用是按照時間片的限制去中斷任務(wù),并通知調(diào)度者再次調(diào)度一個新的執(zhí)行者去繼續(xù)任務(wù)。按照這種認(rèn)知去看它的實(shí)現(xiàn),會很清晰。

const performWorkUntilDeadline = () => {      if (scheduledHostCallback !== null) {        // 獲取當(dāng)前時間        const currentTime = getCurrentTime();        // 計算deadline,deadline會參與到        // shouldYieldToHost(根據(jù)時間片去限制任務(wù)執(zhí)行)的計算中        deadline = currentTime + yieldInterval;        // hasTimeRemaining表示任務(wù)是否還有剩余時間,        // 它和時間片一起限制任務(wù)的執(zhí)行。如果沒有時間,        // 或者任務(wù)的執(zhí)行時間超出時間片限制了,那么中斷任務(wù)。        // 它的默認(rèn)為true,表示一直有剩余時間        // 因?yàn)镸essageChannel的port在postMessage,        // 是比setTimeout還靠前執(zhí)行的宏任務(wù),這意味著        // 在這一幀開始時,總是會有剩余時間        // 所以現(xiàn)在中斷任務(wù)只看時間片的了        const hasTimeRemaining = true;        try {          // scheduledHostCallback去執(zhí)行任務(wù)的函數(shù),          // 當(dāng)任務(wù)因?yàn)闀r間片被打斷時,它會返回true,表示          // 還有任務(wù),所以會再讓調(diào)度者調(diào)度一個執(zhí)行者          // 繼續(xù)執(zhí)行任務(wù)          const hasMoreWork = scheduledHostCallback(            hasTimeRemaining,            currentTime,          );          if (!hasMoreWork) {            // 如果沒有任務(wù)了,停止調(diào)度            isMessageLoopRunning = false;            scheduledHostCallback = null;          } else {            // 如果還有任務(wù),繼續(xù)讓調(diào)度者調(diào)度執(zhí)行者,便于繼續(xù)            // 完成任務(wù)            port.postMessage(null);          }        } catch (error) {          port.postMessage(null);          throw error;        }      } else {        isMessageLoopRunning = false;      }      needsPaint = false;    };

performWorkUntilDeadline內(nèi)部調(diào)用的是scheduledHostCallback,它早在開始調(diào)度的時候就被requestHostCallback賦值為了flushWork,具體可以翻到上面回顧一下requestHostCallback的實(shí)現(xiàn)。

flushWork作為真正去執(zhí)行任務(wù)的函數(shù),它會循環(huán)taskQueue,逐一調(diào)用里面的任務(wù)函數(shù)。我們看一下flushWork具體做了什么。

function flushWork(hasTimeRemaining, initialTime) {    ...    return workLoop(hasTimeRemaining, initialTime);   ...  }

它調(diào)用了workLoop,并將其調(diào)用的結(jié)果return了出去。那么現(xiàn)在任務(wù)執(zhí)行的核心內(nèi)容看來就在workLoop中了。workLoop的調(diào)用使得任務(wù)最終被執(zhí)行。

任務(wù)中斷和恢復(fù)

要理解workLoop,需要回顧Scheduler的功能之一:通過時間片限制任務(wù)的執(zhí)行時間。那么既然任務(wù)的執(zhí)行被限制了,它肯定有可能是尚未完成的,如果未完成被中斷,那么需要將它恢復(fù)。

所以時間片下的任務(wù)執(zhí)行具備下面的重要特點(diǎn):會被中斷,也會被恢復(fù)。

不難推測出,workLoop作為實(shí)際執(zhí)行任務(wù)的函數(shù),它做的事情肯定與任務(wù)的中斷恢復(fù)有關(guān)。我們先看一下它的結(jié)構(gòu):

function workLoop(hasTimeRemaining, initialTime) {    // 獲取taskQueue中排在最前面的任務(wù)    currentTask = peek(taskQueue);    while (currentTask !== null) {      if (currentTask.expirationTime > currentTime &&       (!hasTimeRemaining || shouldYieldToHost())) {         // break掉while循環(huán)         break     }      ...      // 執(zhí)行任務(wù)      ...      // 任務(wù)執(zhí)行完畢,從隊列中刪除      pop(taskQueue);      // 獲取下一個任務(wù),繼續(xù)循環(huán)      currentTask = peek(taskQueue);    }    if (currentTask !== null) {      // 如果currentTask不為空,說明是時間片的限制導(dǎo)致了任務(wù)中斷      // return 一個 true告訴外部,此時任務(wù)還未執(zhí)行完,還有任務(wù),      // 翻譯成英文就是hasMoreWork      return true;    } else {      // 如果currentTask為空,說明taskQueue隊列中的任務(wù)已經(jīng)都      // 執(zhí)行完了,然后從timerQueue中找任務(wù),調(diào)用requestHostTimeout      // 去把task放到taskQueue中,到時會再次發(fā)起調(diào)度,但是這次,      // 會先return false,告訴外部當(dāng)前的taskQueue已經(jīng)清空,      // 先停止執(zhí)行任務(wù),也就是終止任務(wù)調(diào)度     const firstTimer = peek(timerQueue);      if (firstTimer !== null) {        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);      }        return false;    }  }

workLoop中可以分為兩大部分:循環(huán)taskQueue執(zhí)行任務(wù) 和 任務(wù)狀態(tài)的判斷。

循環(huán)taskQueue執(zhí)行任務(wù)

暫且不管任務(wù)如何執(zhí)行,只關(guān)注任務(wù)如何被時間片限制,workLoop中:

if (currentTask.expirationTime > currentTime &&       (!hasTimeRemaining || shouldYieldToHost())) {     // break掉while循環(huán)     break  }

currentTask就是當(dāng)前正在執(zhí)行的任務(wù),它中止的判斷條件是:任務(wù)并未過期,但已經(jīng)沒有剩余時間了(由于hasTimeRemaining一直為true,這與MessageChannel作為宏任務(wù)的執(zhí)行時機(jī)有關(guān),我們忽略這個判斷條件,只看時間片),或者應(yīng)該讓出執(zhí)行權(quán)給主線程(時間片的限制),也就是說currentTask執(zhí)行得好好的,可是時間不允許,那只能先break掉本次while循環(huán),使得本次循環(huán)下面currentTask執(zhí)行的邏輯都不能被執(zhí)行到(此處是中斷任務(wù)的關(guān)鍵)。但是被break的只是while循環(huán),while下部還是會判斷currentTask的狀態(tài)。

由于它只是被中止了,所以currentTask不可能是null,那么會return一個true告訴外部還沒完事呢(此處是恢復(fù)任務(wù)的關(guān)鍵),否則說明全部的任務(wù)都已經(jīng)執(zhí)行完了,taskQueue已經(jīng)被清空了,return一個false好讓外部終止本次調(diào)度。而workLoop的執(zhí)行結(jié)果會被flushWork return出去,flushWork實(shí)際上是scheduledHostCallback,當(dāng)performWorkUntilDeadline檢測到scheduledHostCallback的返回值(hasMoreWork)為false時,就會停止調(diào)度。

回顧performWorkUntilDeadline中的行為,可以很清晰地將任務(wù)中斷恢復(fù)的機(jī)制串聯(lián)起來:

const performWorkUntilDeadline = () => {     ...     const hasTimeRemaining = true;     // scheduledHostCallback去執(zhí)行任務(wù)的函數(shù),     // 當(dāng)任務(wù)因?yàn)闀r間片被打斷時,它會返回true,表示     // 還有任務(wù),所以會再讓調(diào)度者調(diào)度一個執(zhí)行者     // 繼續(xù)執(zhí)行任務(wù)     const hasMoreWork = scheduledHostCallback(       hasTimeRemaining,       currentTime,     );     if (!hasMoreWork) {       // 如果沒有任務(wù)了,停止調(diào)度       isMessageLoopRunning = false;       scheduledHostCallback = null;     } else {       // 如果還有任務(wù),繼續(xù)讓調(diào)度者調(diào)度執(zhí)行者,便于繼續(xù)       // 完成任務(wù)       port.postMessage(null);     }   };

當(dāng)任務(wù)被打斷之后,performWorkUntilDeadline會再讓調(diào)度者調(diào)用一個執(zhí)行者,繼續(xù)執(zhí)行這個任務(wù),直到任務(wù)完成。但是這里有一個重點(diǎn)是如何判斷該任務(wù)是否完成呢?這就需要研究workLoop中執(zhí)行任務(wù)的那部分邏輯。

判斷單個任務(wù)的完成狀態(tài)

任務(wù)的中斷恢復(fù)是一個重復(fù)的過程,該過程會一直重復(fù)到任務(wù)完成。所以判斷任務(wù)是否完成非常重要,而任務(wù)未完成則會重復(fù)執(zhí)行任務(wù)函數(shù)。

我們可以用遞歸函數(shù)做類比,如果沒到遞歸邊界,就重復(fù)調(diào)用自己。這個遞歸邊界,就是任務(wù)完成的標(biāo)志。因?yàn)檫f歸函數(shù)所處理的任務(wù)就是它本身,可以很方便地把任務(wù)完成作為遞歸邊界去結(jié)束任務(wù),但是Scheduler中的workLoop與遞歸不同的是,它只是一個執(zhí)行任務(wù)的,這個任務(wù)并不是它自己產(chǎn)生的,而是外部的(比如它去執(zhí)行React的工作循環(huán)渲染fiber樹),它可以做到重復(fù)執(zhí)行任務(wù)函數(shù),但邊界(即任務(wù)是否完成)卻無法像遞歸那樣直接獲取,只能依賴任務(wù)函數(shù)的返回值去判斷。即:若任務(wù)函數(shù)返回值為函數(shù),那么就說明當(dāng)前任務(wù)尚未完成,需要繼續(xù)調(diào)用任務(wù)函數(shù),否則任務(wù)完成。workLoop就是通過這樣的辦法判斷單個任務(wù)的完成狀態(tài)。

在真正講解workLoop中的執(zhí)行任務(wù)的邏輯之前,我們用一個例子來理解一下判斷任務(wù)完成狀態(tài)的核心。

有一個任務(wù)calculate,負(fù)責(zé)把currentResult每次加1,一直到3為止。當(dāng)沒到3的時候,calculate不是去調(diào)用它自身,而是將自身return出去,一旦到了3,return的是null。這樣外部才可以知道calculate是否已經(jīng)完成了任務(wù)。

const result = 3  let currentResult = 0  function calculate() {      currentResult++      if (currentResult < result) {          return calculate      }      return null  }

上面是任務(wù),接下來我們模擬一下調(diào)度,去執(zhí)行calculate。但執(zhí)行應(yīng)該是基于時間片的,為了觀察效果,只用setInterval去模擬因?yàn)闀r間片中止恢復(fù)任務(wù)的機(jī)制(相當(dāng)粗糙的模擬,只需明白這是時間片的模擬即可,重點(diǎn)關(guān)注任務(wù)完成狀態(tài)的判斷),1秒執(zhí)行它一次,即一次只完成全部任務(wù)的三分之一。

另外Scheduler中有兩個隊列去管理任務(wù),我們暫且只用一個隊列(taskQueue)存儲任務(wù)。除此之外還需要三個角色:把任務(wù)加入調(diào)度的函數(shù)(調(diào)度入口scheduleCallback)、開始調(diào)度的函數(shù)(requestHostCallback)、執(zhí)行任務(wù)的函數(shù)(workLoop,關(guān)鍵邏輯所在)。

const result = 3  let currentResult = 0  function calculate() {      currentResult++      if (currentResult < result) {          return calculate      }      return null  }  // 存放任務(wù)的隊列  const taskQueue = []  // 存放模擬時間片的定時器  let interval  // 調(diào)度入口----------------------------------------  const scheduleCallback = (task, priority) => {      // 創(chuàng)建一個專屬于調(diào)度器的任務(wù)      const taskItem = {          callback: task,          priority      }      // 向隊列中添加任務(wù)      taskQueue.push(taskItem)      // 優(yōu)先級影響到任務(wù)在隊列中的排序,將優(yōu)先級最高的任務(wù)排在最前面      taskQueue.sort((a, b) => (a.priority - b.priority))      // 開始執(zhí)行任務(wù),調(diào)度開始      requestHostCallback(workLoop)  }  // 開始調(diào)度-----------------------------------------  const requestHostCallback = cb => {      interval = setInterval(cb, 1000)  }  // 執(zhí)行任務(wù)-----------------------------------------  const workLoop = () => {      // 從隊列中取出任務(wù)      const currentTask = taskQueue[0]      // 獲取真正的任務(wù)函數(shù),即calculate      const taskCallback = currentTask.callback      // 判斷任務(wù)函數(shù)否是函數(shù),若是,執(zhí)行它,將返回值更新到currentTask的callback中      // 所以,taskCallback是上一階段執(zhí)行的返回值,若它是函數(shù)類型,則說明上一次執(zhí)行返回了函數(shù)      // 類型,說明任務(wù)尚未完成,本次繼續(xù)執(zhí)行這個函數(shù),否則說明任務(wù)完成。      if (typeof taskCallback === 'function') {          currentTask.callback = taskCallback()          console.log('正在執(zhí)行任務(wù),當(dāng)前的currentResult 是', currentResult);      } else {          // 任務(wù)完成。將當(dāng)前的這個任務(wù)從taskQueue中移除,并清除定時器          console.log('任務(wù)完成,最終的 currentResult 是', currentResult);          taskQueue.shift()          clearInterval(interval)      }  }  // 把calculate加入調(diào)度,也就意味著調(diào)度開始  scheduleCallback(calculate, 1)

最終的執(zhí)行結(jié)果如下:

正在執(zhí)行任務(wù),當(dāng)前的currentResult 是 1  正在執(zhí)行任務(wù),當(dāng)前的currentResult 是 2  正在執(zhí)行任務(wù),當(dāng)前的currentResult 是 3  任務(wù)完成,最終的 currentResult 是 3

可見,如果沒有加到3,那么calculate會return它自己,workLoop若判斷返回值為function,說明任務(wù)還未完成,它就會繼續(xù)調(diào)用任務(wù)函數(shù)去完成任務(wù)。

這個例子只保留了workLoop中判斷任務(wù)完成狀態(tài)的邏輯,其余的地方并不完善,要以真正的的workLoop為準(zhǔn),現(xiàn)在讓我們貼出它的全部代碼,完整地看一下真正的實(shí)現(xiàn):

function workLoop(hasTimeRemaining, initialTime) {    let currentTime = initialTime;    // 開始執(zhí)行前檢查一下timerQueue中的過期任務(wù),    // 放到taskQueue中    advanceTimers(currentTime);    // 獲取taskQueue中最緊急的任務(wù)    currentTask = peek(taskQueue);    // 循環(huán)taskQueue,執(zhí)行任務(wù)    while (      currentTask !== null &&      !(enableSchedulerDebugging && isSchedulerPaused)    ) {      if (        currentTask.expirationTime > currentTime &&        (!hasTimeRemaining || shouldYieldToHost())      ) {        // 時間片的限制,中斷任務(wù)        break;      }      // 執(zhí)行任務(wù) ---------------------------------------------------      // 獲取任務(wù)的執(zhí)行函數(shù),這個callback就是React傳給Scheduler      // 的任務(wù)。例如:performConcurrentWorkOnRoot      const callback = currentTask.callback;      if (typeof callback === 'function') {        // 如果執(zhí)行函數(shù)為function,說明還有任務(wù)可做,調(diào)用它        currentTask.callback = null;        // 獲取任務(wù)的優(yōu)先級        currentPriorityLevel = currentTask.priorityLevel;        // 任務(wù)是否過期        const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;        // 獲取任務(wù)函數(shù)的執(zhí)行結(jié)果        const continuationCallback = callback(didUserCallbackTimeout);        if (typeof continuationCallback === 'function') {          // 檢查callback的執(zhí)行結(jié)果返回的是不是函數(shù),如果返回的是函數(shù),則將這個函數(shù)作為當(dāng)前任務(wù)新的回調(diào)。          // concurrent模式下,callback是performConcurrentWorkOnRoot,其內(nèi)部根據(jù)當(dāng)前調(diào)度的任務(wù)          // 是否相同,來決定是否返回自身,如果相同,則說明還有任務(wù)沒做完,返回自身,其作為新的callback          // 被放到當(dāng)前的task上。while循環(huán)完成一次之后,檢查shouldYieldToHost,如果需要讓出執(zhí)行權(quán),          // 則中斷循環(huán),走到下方,判斷currentTask不為null,返回true,說明還有任務(wù),回到performWorkUntilDeadline          // 中,判斷還有任務(wù),繼續(xù)port.postMessage(null),調(diào)用監(jiān)聽函數(shù)performWorkUntilDeadline(執(zhí)行者),          // 繼續(xù)調(diào)用workLoop行任務(wù)          // 將返回值繼續(xù)賦值給currentTask.callback,為得是下一次能夠繼續(xù)執(zhí)行callback,          // 獲取它的返回值,繼續(xù)判斷任務(wù)是否完成。          currentTask.callback = continuationCallback;        } else {          if (currentTask === peek(taskQueue)) {            pop(taskQueue);          }        }        advanceTimers(currentTime);      } else {        pop(taskQueue);      }      // 從taskQueue中繼續(xù)獲取任務(wù),如果上一個任務(wù)未完成,那么它將不會      // 被從隊列剔除,所以獲取到的currentTask還是上一個任務(wù),會繼續(xù)      // 去執(zhí)行它      currentTask = peek(taskQueue);    }    // return 的結(jié)果會作為 performWorkUntilDeadline    // 中判斷是否還需要再次發(fā)起調(diào)度的依據(jù)    if (currentTask !== null) {      return true;    } else {      // 若任務(wù)完成,去timerQueue中找需要最早開始執(zhí)行的那個任務(wù)      // 調(diào)度requestHostTimeout,目的是等到了它的開始事件時把它      // 放到taskQueue中,再次調(diào)度      const firstTimer = peek(timerQueue);      if (firstTimer !== null) {        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);      }      return false;    }  }

所以,workLoop是通過判斷任務(wù)函數(shù)的返回值去識別任務(wù)的完成狀態(tài)的。

總結(jié)一下判斷任務(wù)完成狀態(tài)與任務(wù)執(zhí)行的整體關(guān)系:當(dāng)開始調(diào)度后,調(diào)度者調(diào)度執(zhí)行者去執(zhí)行任務(wù),實(shí)際上是執(zhí)行任務(wù)上的callback(也就是任務(wù)函數(shù))。如果執(zhí)行者判斷callback返回值為一個function,說明未完成,那么會將返回的這個function再次賦值給任務(wù)的callback,由于任務(wù)還未完成,所以并不會被剔除出taskQueue,currentTask獲取到的還是它,while循環(huán)到下一次還是會繼續(xù)執(zhí)行這個任務(wù),直到任務(wù)完成出隊,才會繼續(xù)下一個。

另外有一個點(diǎn)需要提一下,就是構(gòu)建fiber樹的任務(wù)函數(shù):performConcurrentWorkOnRoot,它接受的參數(shù)是fiberRoot。

function performConcurrentWorkOnRoot(root) {    ...  }

在workLoop中它會被這樣調(diào)用(callback即為performConcurrentWorkOnRoot):

const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;  const continuationCallback = callback(didUserCallbackTimeout);

didUserCallbackTimeout明顯是boolean類型的值,并不是fiberRoot,但performConcurrentWorkOnRoot卻能正常調(diào)用。這是因?yàn)樵陂_始調(diào)度,以及后續(xù)的return自身的時候,都在bind的時候?qū)oot傳進(jìn)去了。

// 調(diào)度的時候  scheduleCallback(    schedulerPriorityLevel,    performConcurrentWorkOnRoot.bind(null, root),  );  // 其內(nèi)部return自身的時候  function performConcurrentWorkOnRoot(root) {    ...    if (root.callbackNode === originalCallbackNode) {      return performConcurrentWorkOnRoot.bind(null, root);    }    return null;  }

這樣的話,再給它傳參數(shù)調(diào)用它,那這個參數(shù)只能作為后續(xù)的參數(shù)被接收到,performConcurrentWorkOnRoot中接收到的第一個參數(shù)還是bind時傳入的那個root,這個特點(diǎn)與bind的實(shí)現(xiàn)有關(guān)??梢耘芤幌孪旅娴倪@個簡單例子:

function test(root, b) {      console.log(root, b)  }  function runTest() {     return test.bind(null, 'root')  }  runTest()(false)  // 結(jié)果:root false

以上,是Scheduler執(zhí)行任務(wù)時的兩大核心邏輯:任務(wù)的中斷與恢復(fù) & 任務(wù)完成狀態(tài)的判斷。它們協(xié)同合作,若任務(wù)未完成就中斷了任務(wù),那么調(diào)度的新執(zhí)行者會恢復(fù)執(zhí)行該任務(wù),直到它完成。到此,Scheduler的核心部分已經(jīng)寫完了,下面是取消調(diào)度的邏輯。

取消調(diào)度

通過上面的內(nèi)容我們知道,任務(wù)執(zhí)行實(shí)際上是執(zhí)行的任務(wù)的callback,當(dāng)callback是function的時候去執(zhí)行它,當(dāng)它為null的時候會發(fā)生什么?當(dāng)前的任務(wù)會被剔除出taskQueue,讓我們再來看一下workLoop函數(shù):

function workLoop(hasTimeRemaining, initialTime) {    ...   // 獲取taskQueue中最緊急的任務(wù)    currentTask = peek(taskQueue);    while (currentTask !== null) {      ...      const callback = currentTask.callback;      if (typeof callback === 'function') {        // 執(zhí)行任務(wù)      } else {        // 如果callback為null,將任務(wù)出隊        pop(taskQueue);      }      currentTask = peek(taskQueue);    }    ...  }

所以取消調(diào)度的關(guān)鍵就是將當(dāng)前這個任務(wù)的callback設(shè)置為null。

function unstable_cancelCallback(task) {    ...    task.callback = null;  }

為什么設(shè)置callback為null就能取消任務(wù)調(diào)度呢?因?yàn)樵趙orkLoop中,如果callback是null會被移出taskQueue,所以當(dāng)前的這個任務(wù)就不會再被執(zhí)行了。它取消的是當(dāng)前任務(wù)的執(zhí)行,while循環(huán)還會繼續(xù)執(zhí)行下一個任務(wù)。

取消任務(wù)在React的場景是什么呢?當(dāng)一個更新任務(wù)正在進(jìn)行的時候,突然有高優(yōu)先級任務(wù)進(jìn)來了,那么就要取消掉這個正在進(jìn)行的任務(wù),這只是眾多場景中的一種。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {    ...    if (existingCallbackNode !== null) {      const existingCallbackPriority = root.callbackPriority;      if (existingCallbackPriority === newCallbackPriority) {        return;      }      // 取消掉原有的任務(wù)      cancelCallback(existingCallbackNode);    }    ...  }

總結(jié)

Scheduler用任務(wù)優(yōu)先級去實(shí)現(xiàn)多任務(wù)的管理,優(yōu)先解決高優(yōu)任務(wù),用任務(wù)的持續(xù)調(diào)度來解決時間片造成的單個任務(wù)中斷恢復(fù)問題。任務(wù)函數(shù)的執(zhí)行結(jié)果為是否應(yīng)該結(jié)束當(dāng)前任務(wù)的調(diào)度提供參考,另外,在有限的時間片內(nèi)完成任務(wù)的一部分,也為瀏覽器響應(yīng)交互與完成任務(wù)提供了保障。

到此,關(guān)于“React的調(diào)度機(jī)制原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI