<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實現(xiàn) React requestIdleCallback 調(diào)度能力

          共 7406字,需瀏覽 15分鐘

           ·

          2021-11-07 10:13

          1.前言

          Elab掘金: React Fiber架構(gòu)淺析[1] 已對 React Fiber架構(gòu) 實現(xiàn)進行了淺析。React內(nèi)部實現(xiàn)了該方法 requestIdleCallback,即一幀空閑執(zhí)行任務(wù),但Schedular + Lane 模式遠比 requestIdleCallback 復(fù)雜的多。這里我們先通過了解 requestIdleCallback都做了些什么,再嘗試通過 requestAnimationFrame + MessageChannel 來模擬 React 對一幀空閑判斷的實現(xiàn)。

          2.requestIdleCallback

          window.requestIdleCallback()[2]

          2.1 概念理解

          圖: 簡單描述幀生命周期

          RequestIdleCallback 簡單的說,判斷一幀有空閑時間,則去執(zhí)行某個任務(wù)。

          目的是為了解決當(dāng)任務(wù)需要長時間占用主進程,導(dǎo)致更高優(yōu)先級任務(wù)(如動畫或事件任務(wù)),無法及時響應(yīng),而帶來的頁面丟幀(卡死)情況。

          故RequestIdleCallback 定位處理的是: 不重要且不緊急的任務(wù)。

          RequestIdleCallback 參數(shù)說明:

          • window.requestIdleCallback(callback[, options]); callback為要執(zhí)行的回調(diào)函數(shù),該函數(shù)會接收deadline作為對象。
          //?回調(diào)函數(shù)?接收?deadline

          type?Deadline?=?{

          ? timeRemaining:?()?=> number //?當(dāng)前剩余的可用時間。即該幀剩余時間。

          ? didTimeout: boolean //?是否超時。

          }



          //?接收回調(diào)任務(wù)

          type?RequestIdleCallback?=?(cb:?(deadline:?Deadline)?=>?void,?options?:?Options)?=>?number?

          2.2 實現(xiàn)demo

          requestIdleCallback 處理任務(wù)說明:

          Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/

          Github: RequestIdleCallback 實驗[3]

          const?bindClick?=?id?=>?

          ??element(id).addEventListener('click',?Work.onAsyncUnit)

          //?綁定click事件

          bindClick('btnA')

          bindClick('btnB')

          bindClick('btnC')



          var?Work?=?{

          ????//?有1萬個任務(wù)

          ????unit:?10000,

          ????//?處理單個任務(wù)需要處理如下

          ????onOneUnit:?function?()?{??for?(var?i?=?0;?i?<=?500000;?i++)?{}?},

          ????

          ????//?處理任務(wù)

          ????onAsyncUnit:?function?()?{

          ????????//?空閑時間基準為?1ms

          ????????const?FREE_TIME?=?1

          ????????//?執(zhí)行到第幾個任務(wù)

          ????????let?_u?=?0



          ????????function?cb(deadline)?{

          ????????????//?當(dāng)任務(wù)還沒有被處理完?&?一幀還有的空閑時間?>?1ms

          ????????????while?(_u??FREE_TIME)?{

          ????????????????Work.onOneUnit()

          ????????????????_u?++

          ????????????}

          ????????????//?任務(wù)干完,?執(zhí)行回調(diào)

          ????????????if?(_u?>=?Work.unit)?{

          ????????????????//?執(zhí)行回調(diào)

          ????????????????return

          ????????????}

          ????????????//?任務(wù)沒完成,?繼續(xù)等空閑執(zhí)行

          ????????????window.requestIdleCallback(cb)

          ????????}

          ????????window.requestIdleCallback(cb)

          ????}

          }

          以上是 window.requestIdleCallback 的實現(xiàn)流程。

          核心: 即瀏覽器去在一幀有空閑的情況下,去執(zhí)行某個低優(yōu)先級的任務(wù)。

          2.3 缺陷

          MAY BE OFFTOPIC: requestIdleCallback is called only 20 times per second - Chrome on my 6x2 core Linux machine, it's not really useful for UI work. requestAnimationFrame is called more often, but specific for the task which name suggests.[4]

          • 實驗 api,兼容情況一般。
          • 實驗結(jié)論: requestIdleCallback FPS只有20ms,正常情況下渲染一幀時長控制在16.67ms (1s / 60 = 16.67ms)。該時間是高于頁面流暢的訴求。
          • 個人認為: RequestIdleCallback 不重要且不緊急的定位。因為React渲染內(nèi)容,并非是不重要且不緊急。不僅該api兼容一般,幀渲染能力一般,也不太符合渲染訴求,故React 團隊自行實現(xiàn)。

          3.React requestIdleCallback 實現(xiàn)實驗

          想要實現(xiàn)requestIdleCallback的處理,有2個點需要解決:

          • When: 如何判斷一幀是否有空閑?
          • Where: 如果有了空閑,在一幀中哪里去執(zhí)行任務(wù)?

          3.1 requestAnimationFrame 計算一幀到期時間點

          requestAnimationFrame[5]

          • 是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機。 它會把每一幀中的所有DOM操作集中起來,在一次重繪或回流中就完成,并且重繪或回流的時間間隔緊緊跟隨屏幕的刷新頻率,不會引起丟幀和卡頓。

          • 瀏覽器刷新率在60Hz, 渲染一幀時長控制在16.67ms (1s / 60 = 16.67ms)。

          DOMHighResTimeStamp[6]

          requestAnimationFrame 參數(shù)如下:

          //?回調(diào)函數(shù)?接收?rafTime?即?開始執(zhí)行一幀的開始時間

          //?接收回調(diào)任務(wù)

          type?RequestAnimationFrame?=?(cb:?(rafTime:?number)?=>?void)

          計算一幀用到期的時間點。

          //?計算出當(dāng)前幀?結(jié)束時間

          var?deadlineTime;

          window.selfRequestIdleCallback?=?function?(cb)?{

          ????requestAnimationFrame(rafTime?=>?{

          ????????//?結(jié)束時間?=?開始時間?+?一幀用時16.667ms

          ????????deadlineTime?=?rafTime?+?16.667

          ????????//?......?

          ????})

          }

          以上使用 requestAnimationFrame 來計算結(jié)束的時間點。

          我們暫且將空閑時間的判斷放到后面去解決,先來看在時間充裕情況下,在什么時機去執(zhí)行某任務(wù)。

          3.2 MessageChannel 宏任務(wù) 執(zhí)行任務(wù)

          MessageChannel()[7]

          MessageChannel創(chuàng)建了一個通信的管道,這個管道有兩個端口,每個端口都可以通過postMessage發(fā)送數(shù)據(jù),而一個端口只要綁定了onmessage回調(diào)方法,就可以接收從另一個端口傳過來的數(shù)據(jù)。

          在看著方法實現(xiàn)之前,你可能有疑問:

          1. 為什么使用宏任務(wù)處理呢?

          核心是將主進程讓出,將瀏覽器去更新頁面。

          利用事件循環(huán)機制,在下一幀宏任務(wù)的時候,執(zhí)行未完成的任務(wù)。

          1. 為什么不是微任務(wù)?

          走遠了。對一個事件循環(huán)機制來說,在頁面更新前,會將所有的微任務(wù)全部執(zhí)行完,故無法達成將主線程讓出給瀏覽器的目的。

          1. 既然用了宏任務(wù),那為什么不使用 setTimeout 宏任務(wù)執(zhí)行呢?
          • 如果不支持MessageChannel的話,就會去用 setTimeout 來執(zhí)行,只是退而求其次的辦法。
          • 現(xiàn)實情況是: 瀏覽器在執(zhí)行 setTimeout()setInterval() 時,會設(shè)定一個最小的時間閾值,一般是 4ms。
          var?i?=?0

          var?_start?=?+new?Date()

          function?fn()?{

          ??setTimeout(()?=>?{

          ????console.log("執(zhí)行次數(shù),?時間",?++i,?+new?Date()?-?_start)

          ????if?(i?===?10)?{

          ??????return

          ????}

          ????fn()

          ??},?0)

          }

          fn()

          故,利用MessageChannel來執(zhí)行宏任務(wù),且模擬setTimeout(fn, 0),還沒有時延哦。

          實現(xiàn)如下:



          //?計算出當(dāng)前幀?結(jié)束時間點

          var?deadlineTime

          //?保存任務(wù)

          var?callback

          //?建立通信

          var?channel?=?new?MessageChannel()

          var?port1?=?channel.port1;

          var?port2?=?channel.port2;



          //?接收并執(zhí)行宏任務(wù)

          port2.onmessage?=?()?=>?{

          ????//?判斷當(dāng)前幀是否還有空閑,即返回的是剩下的時間

          ????const?timeRemaining?=?()?=>?deadlineTime?-?performance.now();

          ????const?_timeRemain?=?timeRemaining();

          ????//?有空閑時間?且?有回調(diào)任務(wù)

          ????if?(_timeRemain?>?0?&&?callback)?{

          ????????const?deadline?=?{

          ????????????timeRemaining,?//?計算剩余時間

          ????????????didTimeout:?_timeRemain?
          ????????}

          ????????//?執(zhí)行回調(diào)

          ????????callback(deadline)

          ????}

          }

          window.requestIdleCallback?=?function?(cb)?{

          ????requestAnimationFrame(rafTime?=>?{

          ????????//?結(jié)束時間點?=?開始時間點?+?一幀用時16.667ms

          ????????deadlineTime?=?rafTime?+?16.667

          ????????//?保存任務(wù)

          ????????callback?=?cb

          ????????//?發(fā)送個宏任務(wù)

          ????????port1.postMessage(null);

          ????})

          }

          4.React 源碼 requestHostCallback

          SchedulerHostConfig.js[8]

          執(zhí)行宏任務(wù)(回調(diào)任務(wù))

          • requestHostCallback: 觸發(fā)一個宏任務(wù) performWorkUntilDeadline。

          • performWorkUntilDeadline: 宏任務(wù)處理。

            • 是否有富裕時間, 有則執(zhí)行。
            • 執(zhí)行該回調(diào)任務(wù)后,是否還有下一個回調(diào)任務(wù), 即判斷 hasMoreWork。
            • 有則繼續(xù)執(zhí)行 port.postMessage(null);
          ??let?scheduledHostCallback?=?null;

          ??let?isMessageLoopRunning?=?false;

          ?
          ??const?channel?=?new?MessageChannel();

          ??//?port2?發(fā)送

          ??const?port?=?channel.port2;

          ??//?port1?接收

          ??channel.port1.onmessage?=?performWorkUntilDeadline;

          ??const?performWorkUntilDeadline?=?()?=>?{

          ????//?有執(zhí)行任務(wù)

          ????if?(scheduledHostCallback?!==?null)?{

          ??????const?currentTime?=?getCurrentTime();

          ??????//?Yield?after?`yieldInterval`?ms,?regardless?of?where?we?are?in?the?vsync

          ??????//?cycle.?This?means?there's?always?time?remaining?at?the?beginning?of

          ??????//?the?message?event.

          ??????//?計算一幀的過期時間點

          ??????deadline?=?currentTime?+?yieldInterval;

          ??????const?hasTimeRemaining?=?true;

          ??????try?{

          ????????//?執(zhí)行完該回調(diào)后,?判斷后續(xù)是否還有其他任務(wù)

          ????????const?hasMoreWork?=?scheduledHostCallback(

          ??????????hasTimeRemaining,

          ??????????currentTime,

          ????????);

          ????????if?(!hasMoreWork)?{

          ??????????isMessageLoopRunning?=?false;

          ??????????scheduledHostCallback?=?null;

          ????????}?else?{

          ??????????//?If?there'
          s?more?work,?schedule?the?next?message?event?at?the?end

          ??????????//?of?the?preceding?one.

          ??????????//?還有其他任務(wù),?推進進入下一個宏任務(wù)隊列中

          ??????????port.postMessage(null);

          ????????}

          ??????}?catch?(error)?{

          ????????//?If?a?scheduler?task?throws,?exit?the?current?browser?task?so?the

          ????????//?error?can?be?observed.

          ????????port.postMessage(null);

          ????????throw?error;

          ??????}

          ????}?else?{

          ??????isMessageLoopRunning?=?false;

          ????}

          ????//?Yielding?to?the?browser?will?give?it?a?chance?to?paint,?so?we?can

          ????//?reset?this.

          ????needsPaint?=?false;

          ??};

          ??//?requestHostCallback?一幀中執(zhí)行任務(wù)

          ??requestHostCallback?=?function(callback)?{

          ????//?回調(diào)注冊

          ????scheduledHostCallback?=?callback;

          ????if?(!isMessageLoopRunning)?{

          ??????isMessageLoopRunning?=?true;

          ??????//?進入宏任務(wù)隊列

          ??????port.postMessage(null);

          ????}

          ??};

          ??cancelHostCallback?=?function()?{

          ????scheduledHostCallback?=?null;

          ??};

          參考資料

          [1]

          Elab掘金: React Fiber架構(gòu)淺析: https://juejin.cn/post/7005880269827735566

          [2]

          window.requestIdleCallback(): https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

          [3]

          RequestIdleCallback 實驗: https://github.com/Linjiayu6/FE-RequestIdleCallback-demo

          [4]

          MAY BE OFFTOPIC: requestIdleCallback is called only 20 times per second - Chrome on my 6x2 core Linux machine, it's not really useful for UI work. requestAnimationFrame is called more often, but specific for the task which name suggests.: https://github.com/facebook/react/issues/13206#issuecomment-418923831

          [5]

          requestAnimationFrame: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

          [6]

          DOMHighResTimeStamp: https://developer.mozilla.org/zh-CN/docs/Web/API/DOMHighResTimeStamp

          [7]

          MessageChannel(): https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel/MessageChannel

          [8]

          SchedulerHostConfig.js: https://github.com/facebook/react/blob/v17.0.1/packages/scheduler/src/forks/SchedulerHostConfig.default.js


          往期推薦


          2021 TWeb 騰訊前端技術(shù)大會精彩回顧(附PPT)
          面試題:說說事件循環(huán)機制(滿分答案來了)
          專心工作只想搞錢的前端女程序員的2020

          最后


          • 歡迎加我微信,拉你進技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認真學(xué)前端,做個專業(yè)的技術(shù)人...

          點個在看支持我吧
          瀏覽 78
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  噜噜噜久久亚洲精品国产品 | 日韩黄片 | 东京热在线观看 | av无码av | 爱爱综合在线 |