自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

[[431408]]

本文轉(zhuǎn)載自微信公眾號「ELab團隊」,作者ELab.lijiayu 。轉(zhuǎn)載本文請聯(lián)系ELab團隊公眾號。

1.前言

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

2.requestIdleCallback

window.requestIdleCallback()[2]

2.1 概念理解

圖: 簡單描述幀生命周期

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

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

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

RequestIdleCallback 參數(shù)說明:

  • window.requestIdleCallback(callback[, options]); callback為要執(zhí)行的回調(diào)函數(shù),該函數(shù)會接收deadline作為對象。
  1. // 回調(diào)函數(shù) 接收 deadline 
  2.  
  3. type Deadline = { 
  4.  
  5.   timeRemaining: () => number // 當(dāng)前剩余的可用時間。即該幀剩余時間。 
  6.  
  7.   didTimeout: boolean // 是否超時。 
  8.  
  9.  
  10.  
  11.  
  12. // 接收回調(diào)任務(wù) 
  13.  
  14. 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]

  1. const bindClick = id =>  
  2.  
  3.   element(id).addEventListener('click'Work.onAsyncUnit) 
  4.  
  5. // 綁定click事件 
  6.  
  7. bindClick('btnA'
  8.  
  9. bindClick('btnB'
  10.  
  11. bindClick('btnC'
  12.  
  13.  
  14.  
  15. var Work = { 
  16.  
  17.     // 有1萬個任務(wù) 
  18.  
  19.     unit: 10000, 
  20.  
  21.     // 處理單個任務(wù)需要處理如下 
  22.  
  23.     onOneUnit: function () {  for (var i = 0; i <= 500000; i++) {} }, 
  24.  
  25.      
  26.  
  27.     // 處理任務(wù) 
  28.  
  29.     onAsyncUnit: function () { 
  30.  
  31.         // 空閑時間基準(zhǔn)為 1ms 
  32.  
  33.         const FREE_TIME = 1 
  34.  
  35.         // 執(zhí)行到第幾個任務(wù) 
  36.  
  37.         let _u = 0 
  38.  
  39.  
  40.  
  41.         function cb(deadline) { 
  42.  
  43.             // 當(dāng)任務(wù)還沒有被處理完 & 一幀還有的空閑時間 > 1ms 
  44.  
  45.             while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) { 
  46.  
  47.                 Work.onOneUnit() 
  48.  
  49.                 _u ++ 
  50.  
  51.             } 
  52.  
  53.             // 任務(wù)干完, 執(zhí)行回調(diào) 
  54.  
  55.             if (_u >= Work.unit) { 
  56.  
  57.                 // 執(zhí)行回調(diào) 
  58.  
  59.                 return 
  60.  
  61.             } 
  62.  
  63.             // 任務(wù)沒完成, 繼續(xù)等空閑執(zhí)行 
  64.  
  65.             window.requestIdleCallback(cb) 
  66.  
  67.         } 
  68.  
  69.         window.requestIdleCallback(cb) 
  70.  
  71.     } 
  72.  

以上是 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)。該時間是高于頁面流暢的訴求。
  • 個人認(rèn)為: 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ù)如下:

  1. // 回調(diào)函數(shù) 接收 rafTime 即 開始執(zhí)行一幀的開始時間 
  2.  
  3. // 接收回調(diào)任務(wù) 
  4.  
  5. type RequestAnimationFrame = (cb: (rafTime: number) => void) 

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

  1. // 計算出當(dāng)前幀 結(jié)束時間 
  2.  
  3. var deadlineTime; 
  4.  
  5. window.selfRequestIdleCallback = function (cb) { 
  6.  
  7.     requestAnimationFrame(rafTime => { 
  8.  
  9.         // 結(jié)束時間 = 開始時間 + 一幀用時16.667ms 
  10.  
  11.         deadlineTime = rafTime + 16.667 
  12.  
  13.         // ......  
  14.  
  15.     }) 
  16.  

以上使用 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)之前,你可能有疑問:

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

核心是將主進(jìn)程讓出,將瀏覽器去更新頁面。

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

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

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

既然用了宏任務(wù),那為什么不使用 setTimeout 宏任務(wù)執(zhí)行呢?

如果不支持MessageChannel的話,就會去用 setTimeout 來執(zhí)行,只是退而求其次的辦法。

現(xiàn)實情況是: 瀏覽器在執(zhí)行 setTimeout() 和 setInterval() 時,會設(shè)定一個最小的時間閾值,一般是 4ms。

  1. var i = 0 
  2.  
  3. var _start = +new Date() 
  4.  
  5. function fn() { 
  6.  
  7.   setTimeout(() => { 
  8.  
  9.     console.log("執(zhí)行次數(shù), 時間", ++i, +new Date() - _start) 
  10.  
  11.     if (i === 10) { 
  12.  
  13.       return 
  14.  
  15.     } 
  16.  
  17.     fn() 
  18.  
  19.   }, 0) 
  20.  
  21.  
  22. fn() 

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

實現(xiàn)如下:

  1. // 計算出當(dāng)前幀 結(jié)束時間點 
  2.  
  3. var deadlineTime 
  4.  
  5. // 保存任務(wù) 
  6.  
  7. var callback 
  8.  
  9. // 建立通信 
  10.  
  11. var channel = new MessageChannel() 
  12.  
  13. var port1 = channel.port1; 
  14.  
  15. var port2 = channel.port2; 
  16.  
  17.  
  18.  
  19. // 接收并執(zhí)行宏任務(wù) 
  20.  
  21. port2.onmessage = () => { 
  22.  
  23.     // 判斷當(dāng)前幀是否還有空閑,即返回的是剩下的時間 
  24.  
  25.     const timeRemaining = () => deadlineTime - performance.now(); 
  26.  
  27.     const _timeRemain = timeRemaining(); 
  28.  
  29.     // 有空閑時間 且 有回調(diào)任務(wù) 
  30.  
  31.     if (_timeRemain > 0 && callback) { 
  32.  
  33.         const deadline = { 
  34.  
  35.             timeRemaining, // 計算剩余時間 
  36.  
  37.             didTimeout: _timeRemain < 0 // 當(dāng)前幀是否完成 
  38.  
  39.         } 
  40.  
  41.         // 執(zhí)行回調(diào) 
  42.  
  43.         callback(deadline) 
  44.  
  45.     } 
  46.  
  47.  
  48. window.requestIdleCallback = function (cb) { 
  49.  
  50.     requestAnimationFrame(rafTime => { 
  51.  
  52.         // 結(jié)束時間點 = 開始時間點 + 一幀用時16.667ms 
  53.  
  54.         deadlineTime = rafTime + 16.667 
  55.  
  56.         // 保存任務(wù) 
  57.  
  58.         callback = cb 
  59.  
  60.         // 發(fā)送個宏任務(wù) 
  61.  
  62.         port1.postMessage(null); 
  63.  
  64.     }) 
  65.  

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);
  1. let scheduledHostCallback = null
  2.  
  3. let isMessageLoopRunning = false
  4.  
  5.  
  6. const channel = new MessageChannel(); 
  7.  
  8. // port2 發(fā)送 
  9.  
  10. const port = channel.port2; 
  11.  
  12. // port1 接收 
  13.  
  14. channel.port1.onmessage = performWorkUntilDeadline; 
  15.  
  16. const performWorkUntilDeadline = () => { 
  17.  
  18.   // 有執(zhí)行任務(wù) 
  19.  
  20.   if (scheduledHostCallback !== null) { 
  21.  
  22.     const currentTime = getCurrentTime(); 
  23.  
  24.     // Yield after `yieldInterval` ms, regardless of where we are in the vsync 
  25.  
  26.     // cycle. This means there's always time remaining at the beginning of 
  27.  
  28.     // the message event. 
  29.  
  30.     // 計算一幀的過期時間點 
  31.  
  32.     deadline = currentTime + yieldInterval; 
  33.  
  34.     const hasTimeRemaining = true
  35.  
  36.     try { 
  37.  
  38.       // 執(zhí)行完該回調(diào)后, 判斷后續(xù)是否還有其他任務(wù) 
  39.  
  40.       const hasMoreWork = scheduledHostCallback( 
  41.  
  42.         hasTimeRemaining, 
  43.  
  44.         currentTime, 
  45.  
  46.       ); 
  47.  
  48.       if (!hasMoreWork) { 
  49.  
  50.         isMessageLoopRunning = false
  51.  
  52.         scheduledHostCallback = null
  53.  
  54.       } else { 
  55.  
  56.         // If there's more work, schedule the next message event at the end 
  57.  
  58.         // of the preceding one. 
  59.  
  60.         // 還有其他任務(wù), 推進(jìn)進(jìn)入下一個宏任務(wù)隊列中 
  61.  
  62.         port.postMessage(null); 
  63.  
  64.       } 
  65.  
  66.     } catch (error) { 
  67.  
  68.       // If a scheduler task throws, exit the current browser task so the 
  69.  
  70.       // error can be observed. 
  71.  
  72.       port.postMessage(null); 
  73.  
  74.       throw error; 
  75.  
  76.     } 
  77.  
  78.   } else { 
  79.  
  80.     isMessageLoopRunning = false
  81.  
  82.   } 
  83.  
  84.   // Yielding to the browser will give it a chance to paint, so we can 
  85.  
  86.   // reset this. 
  87.  
  88.   needsPaint = false
  89.  
  90. }; 
  91.  
  92. // requestHostCallback 一幀中執(zhí)行任務(wù) 
  93.  
  94. requestHostCallback = function(callback) { 
  95.  
  96.   // 回調(diào)注冊 
  97.  
  98.   scheduledHostCallback = callback; 
  99.  
  100.   if (!isMessageLoopRunning) { 
  101.  
  102.     isMessageLoopRunning = true
  103.  
  104.     // 進(jìn)入宏任務(wù)隊列 
  105.  
  106.     port.postMessage(null); 
  107.  
  108.   } 
  109.  
  110. }; 
  111.  
  112. cancelHostCallback = function() { 
  113.  
  114.   scheduledHostCallback = null
  115.  
  116. }; 

參考資料

[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

 

責(zé)任編輯:武曉燕 來源: ELab團隊
相關(guān)推薦

2021-12-16 06:21:16

React組件前端

2022-12-06 08:30:06

SchedulerReact

2009-10-13 10:37:14

自然災(zāi)害應(yīng)急預(yù)案

2021-12-26 12:10:21

React組件前端

2023-04-17 08:13:13

KubernetesPod

2022-01-10 08:31:29

React組件前端

2023-05-08 16:38:46

任務(wù)調(diào)度分布式任務(wù)調(diào)度

2010-07-29 11:34:31

多媒體調(diào)度系統(tǒng)MDS捷思銳

2023-04-07 15:12:46

ReactReact-Intl

2022-05-15 22:08:58

ReactHookdebounce

2010-08-12 15:38:39

IT運維網(wǎng)管軟件摩卡軟件

2023-12-26 07:44:00

Spring定時調(diào)度

2021-02-01 11:30:13

React前端調(diào)度

2022-07-06 08:30:36

vuereactvdom

2020-10-28 09:12:48

React架構(gòu)Hooks

2020-10-21 08:38:47

React源碼

2023-03-01 09:39:40

調(diào)度系統(tǒng)

2024-10-21 09:18:47

2022-04-16 20:10:00

React Hookfiber框架
點贊
收藏

51CTO技術(shù)棧公眾號