成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

實現 React requestIdleCallback 調度能力

開發 架構
React內部實現了該方法 requestIdleCallback,即一幀空閑執行任務,但Schedular + Lane 模式遠比 requestIdleCallback 復雜的多。這里我們先通過了解 requestIdleCallback都做了些什么,再嘗試通過 requestAnimationFrame + MessageChannel 來模擬 React 對一幀空閑判斷的實現。

[[431408]]

本文轉載自微信公眾號「ELab團隊」,作者ELab.lijiayu 。轉載本文請聯系ELab團隊公眾號。

1.前言

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

2.requestIdleCallback

window.requestIdleCallback()[2]

2.1 概念理解

圖: 簡單描述幀生命周期

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

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

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

RequestIdleCallback 參數說明:

  • window.requestIdleCallback(callback[, options]); callback為要執行的回調函數,該函數會接收deadline作為對象。
  1. // 回調函數 接收 deadline 
  2.  
  3. type Deadline = { 
  4.  
  5.   timeRemaining: () => number // 當前剩余的可用時間。即該幀剩余時間。 
  6.  
  7.   didTimeout: boolean // 是否超時。 
  8.  
  9.  
  10.  
  11.  
  12. // 接收回調任務 
  13.  
  14. type RequestIdleCallback = (cb: (deadline: Deadline) => void, options?: Options) => number  

2.2 實現demo

requestIdleCallback 處理任務說明:

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萬個任務 
  18.  
  19.     unit: 10000, 
  20.  
  21.     // 處理單個任務需要處理如下 
  22.  
  23.     onOneUnit: function () {  for (var i = 0; i <= 500000; i++) {} }, 
  24.  
  25.      
  26.  
  27.     // 處理任務 
  28.  
  29.     onAsyncUnit: function () { 
  30.  
  31.         // 空閑時間基準為 1ms 
  32.  
  33.         const FREE_TIME = 1 
  34.  
  35.         // 執行到第幾個任務 
  36.  
  37.         let _u = 0 
  38.  
  39.  
  40.  
  41.         function cb(deadline) { 
  42.  
  43.             // 當任務還沒有被處理完 & 一幀還有的空閑時間 > 1ms 
  44.  
  45.             while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) { 
  46.  
  47.                 Work.onOneUnit() 
  48.  
  49.                 _u ++ 
  50.  
  51.             } 
  52.  
  53.             // 任務干完, 執行回調 
  54.  
  55.             if (_u >= Work.unit) { 
  56.  
  57.                 // 執行回調 
  58.  
  59.                 return 
  60.  
  61.             } 
  62.  
  63.             // 任務沒完成, 繼續等空閑執行 
  64.  
  65.             window.requestIdleCallback(cb) 
  66.  
  67.         } 
  68.  
  69.         window.requestIdleCallback(cb) 
  70.  
  71.     } 
  72.  

以上是 window.requestIdleCallback 的實現流程。

核心: 即瀏覽器去在一幀有空閑的情況下,去執行某個低優先級的任務。

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

3.React requestIdleCallback 實現實驗

想要實現requestIdleCallback的處理,有2個點需要解決:

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

3.1 requestAnimationFrame 計算一幀到期時間點

requestAnimationFrame[5]

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

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

DOMHighResTimeStamp[6]

requestAnimationFrame 參數如下:

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

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

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

以上使用 requestAnimationFrame 來計算結束的時間點。

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

3.2 MessageChannel 宏任務 執行任務

MessageChannel()[7]

MessageChannel創建了一個通信的管道,這個管道有兩個端口,每個端口都可以通過postMessage發送數據,而一個端口只要綁定了onmessage回調方法,就可以接收從另一個端口傳過來的數據。

在看著方法實現之前,你可能有疑問:

為什么使用宏任務處理呢?

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

利用事件循環機制,在下一幀宏任務的時候,執行未完成的任務。

為什么不是微任務?

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

既然用了宏任務,那為什么不使用 setTimeout 宏任務執行呢?

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

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

  1. var i = 0 
  2.  
  3. var _start = +new Date() 
  4.  
  5. function fn() { 
  6.  
  7.   setTimeout(() => { 
  8.  
  9.     console.log("執行次數, 時間", ++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來執行宏任務,且模擬setTimeout(fn, 0),還沒有時延哦。

實現如下:

  1. // 計算出當前幀 結束時間點 
  2.  
  3. var deadlineTime 
  4.  
  5. // 保存任務 
  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. // 接收并執行宏任務 
  20.  
  21. port2.onmessage = () => { 
  22.  
  23.     // 判斷當前幀是否還有空閑,即返回的是剩下的時間 
  24.  
  25.     const timeRemaining = () => deadlineTime - performance.now(); 
  26.  
  27.     const _timeRemain = timeRemaining(); 
  28.  
  29.     // 有空閑時間 且 有回調任務 
  30.  
  31.     if (_timeRemain > 0 && callback) { 
  32.  
  33.         const deadline = { 
  34.  
  35.             timeRemaining, // 計算剩余時間 
  36.  
  37.             didTimeout: _timeRemain < 0 // 當前幀是否完成 
  38.  
  39.         } 
  40.  
  41.         // 執行回調 
  42.  
  43.         callback(deadline) 
  44.  
  45.     } 
  46.  
  47.  
  48. window.requestIdleCallback = function (cb) { 
  49.  
  50.     requestAnimationFrame(rafTime => { 
  51.  
  52.         // 結束時間點 = 開始時間點 + 一幀用時16.667ms 
  53.  
  54.         deadlineTime = rafTime + 16.667 
  55.  
  56.         // 保存任務 
  57.  
  58.         callback = cb 
  59.  
  60.         // 發送個宏任務 
  61.  
  62.         port1.postMessage(null); 
  63.  
  64.     }) 
  65.  

4.React 源碼 requestHostCallback

SchedulerHostConfig.js[8]

執行宏任務(回調任務)

  • requestHostCallback: 觸發一個宏任務 performWorkUntilDeadline。
  • performWorkUntilDeadline: 宏任務處理。
    • 是否有富裕時間, 有則執行。
    • 執行該回調任務后,是否還有下一個回調任務, 即判斷 hasMoreWork。
    • 有則繼續執行 port.postMessage(null);
  1. let scheduledHostCallback = null
  2.  
  3. let isMessageLoopRunning = false
  4.  
  5.  
  6. const channel = new MessageChannel(); 
  7.  
  8. // port2 發送 
  9.  
  10. const port = channel.port2; 
  11.  
  12. // port1 接收 
  13.  
  14. channel.port1.onmessage = performWorkUntilDeadline; 
  15.  
  16. const performWorkUntilDeadline = () => { 
  17.  
  18.   // 有執行任務 
  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.       // 執行完該回調后, 判斷后續是否還有其他任務 
  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.         // 還有其他任務, 推進進入下一個宏任務隊列中 
  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 一幀中執行任務 
  93.  
  94. requestHostCallback = function(callback) { 
  95.  
  96.   // 回調注冊 
  97.  
  98.   scheduledHostCallback = callback; 
  99.  
  100.   if (!isMessageLoopRunning) { 
  101.  
  102.     isMessageLoopRunning = true
  103.  
  104.     // 進入宏任務隊列 
  105.  
  106.     port.postMessage(null); 
  107.  
  108.   } 
  109.  
  110. }; 
  111.  
  112. cancelHostCallback = function() { 
  113.  
  114.   scheduledHostCallback = null
  115.  
  116. }; 

參考資料

[1]Elab掘金: React Fiber架構淺析: 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

 

責任編輯:武曉燕 來源: ELab團隊
相關推薦

2021-12-16 06:21:16

React組件前端

2022-12-06 08:30:06

SchedulerReact

2009-10-13 10:37:14

自然災害應急預案

2021-12-26 12:10:21

React組件前端

2023-04-17 08:13:13

KubernetesPod

2022-01-10 08:31:29

React組件前端

2010-07-29 11:34:31

多媒體調度系統MDS捷思銳

2023-05-08 16:38:46

任務調度分布式任務調度

2023-04-07 15:12:46

ReactReact-Intl

2022-05-15 22:08:58

ReactHookdebounce

2010-08-12 15:38:39

IT運維網管軟件摩卡軟件

2023-12-26 07:44:00

Spring定時調度

2021-02-01 11:30:13

React前端調度

2022-07-06 08:30:36

vuereactvdom

2020-10-28 09:12:48

React架構Hooks

2020-10-21 08:38:47

React源碼

2024-10-21 09:18:47

2023-03-01 09:39:40

調度系統

2022-02-08 12:30:30

React事件系統React事件系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲国产一区二区视频 | 激情婷婷 | 精品一区二区在线视频 | 中文在线一区二区 | 成人av在线播放 | 韩日在线观看视频 | 亚洲国产高清免费 | 日韩中文字幕在线视频 | 日本黄色不卡视频 | 国外成人在线视频 | 欧美午夜剧场 | 久久久精品一区二区三区四季av | 韩日精品一区 | 久久久久久久久久久久亚洲 | 欧美一区免费 | 日韩在线不卡 | 欧美一区永久视频免费观看 | 精精国产xxxx视频在线播放7 | 在线视频久久 | 久久国产高清 | 精品视频在线一区 | 亚洲精品久久久 | 日日操操 | 精品国产乱码久久久久久牛牛 | 欧美h视频 | 欧美一区二区三区在线播放 | 一区二区三区免费网站 | 成人在线观看网站 | 亚洲网址在线观看 | 中文字幕在线免费观看 | 午夜欧美a级理论片915影院 | 精品国产一区二区三区性色 | 欧美 日韩 在线播放 | 日韩在线不卡视频 | 伊人久久免费视频 | 日本三级做a全过程在线观看 | 每日更新av | 国产精品一区二区不卡 | 亚洲午夜精品视频 | 日韩成人中文字幕 | 一区二区三区国产精品 |