<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>

          從中斷機(jī)制看 React Fiber 技術(shù)

          共 4288字,需瀏覽 9分鐘

           ·

          2021-04-06 19:28

          前言

          React 16 開(kāi)始,采用了 Fiber 機(jī)制替代了原有的同步渲染 VDOM 的方案,提高了頁(yè)面渲染性能和用戶體驗(yàn)。Fiber 究竟是什么,網(wǎng)上也很多優(yōu)秀的技術(shù)揭秘文章,本篇主要想從計(jì)算機(jī)的中斷機(jī)制來(lái)聊聊 React Fiber 技術(shù)大概工作原理。

          單任務(wù)

          在早期的單任務(wù)系統(tǒng)上,用戶一次只能提交一個(gè)任務(wù),當(dāng)前運(yùn)行的任務(wù)擁有全部硬件和軟件資源,如果任務(wù)不主動(dòng)釋放 CPU 控制權(quán),那么將一直占用所有資源,可能影響其他任務(wù),造成資源浪費(fèi)。該模式非常像當(dāng)前瀏覽器運(yùn)行模式,由于 UI 線程和 JS 線程的運(yùn)行是互斥的,一旦 JS 長(zhǎng)時(shí)間執(zhí)行,瀏覽器無(wú)法及時(shí)響應(yīng)用戶交互,很容造成界面的卡頓,React 早期的同步渲染機(jī)制,當(dāng)一次性更新的節(jié)點(diǎn)太多時(shí),影響用戶體驗(yàn)。


          中斷

          中斷最初是用于提高處理器效率的一種手段,在沒(méi)有中斷的情況下,當(dāng) CPU 在執(zhí)行一段代碼時(shí),如果程序不主動(dòng)退出(如:一段無(wú)限循環(huán)代碼),那么 CPU 將被一直占用,影響其他任務(wù)運(yùn)行。

          while(true) {
          ...
          };

          而中斷機(jī)制會(huì)強(qiáng)制中斷當(dāng)前 CPU 所執(zhí)行的代碼,轉(zhuǎn)而去執(zhí)行先前注冊(cè)好的中斷服務(wù)程序。比較常見(jiàn)的如:時(shí)鐘中斷,它每隔一定時(shí)間將中斷當(dāng)前正在執(zhí)行的任務(wù),并立刻執(zhí)行預(yù)先設(shè)置的中斷服務(wù)程序,從而實(shí)現(xiàn)不同任務(wù)之間的交替執(zhí)行,這也是在多任務(wù)系統(tǒng)的重要的基礎(chǔ)機(jī)制。中斷機(jī)制主要通過(guò)硬件觸發(fā),CPU 屬于被動(dòng)接受。有了中斷后,各任務(wù)執(zhí)行時(shí)間就可以得到非常好的控制。

          回到瀏覽器,目前瀏覽器大多是 60Hz(60 幀/秒),既每一幀耗時(shí)大概在 16ms 左右,它會(huì)經(jīng)過(guò)下面這幾個(gè)過(guò)程:


          1. 輸入事件處理
          2. requestAnimationFrame
          3. DOM 渲染
          4. RIC (RequestIdleCallback)

          我們除了在步驟 1-3 的中進(jìn)行加塞外,無(wú)法進(jìn)行任何干預(yù),而步驟 4 的 RIC,算是一種防止多余計(jì)算資源被浪費(fèi)的機(jī)制,例如,當(dāng)一幀中步驟 1-3 只耗費(fèi) 6ms,那么剩余 10ms 的計(jì)算資源則會(huì)被浪費(fèi),而 RIC 就是瀏覽器提供的一種資源利用的接口。RIC 非常像前面提到的“中斷服務(wù)”,而瀏覽器的每一幀類(lèi)似“中斷機(jī)制”,利用它則可以在實(shí)現(xiàn)我們前面提到的大任務(wù)卡頓問(wèn)題,例如:之前我們?cè)?JS 中寫(xiě)如下代碼時(shí),無(wú)疑會(huì)阻塞瀏覽器渲染。

          function task(){
            while(true){
             ...
            };
          }
          task();

          但利用 RIC 機(jī)制后,我們完全可以讓大任務(wù)周期性的執(zhí)行,從而不阻止瀏覽器正常渲染。

          將上面示例代碼根據(jù) RequestIdleCallback 進(jìn)行調(diào)整,如下:

          function task(){
            while(true){
             ...
            };
          }
          requestIdleCallback(task);

          遺憾的是,由于我們的代碼運(yùn)行在用戶態(tài),無(wú)法感知到底層的真實(shí)中斷,我們現(xiàn)在利用的 RIC 也只是一種中斷的近似模擬,以上代碼并不會(huì)在 16ms 到期后被強(qiáng)制中斷,我們只能主動(dòng)進(jìn)行釋放,將控制權(quán)交還瀏覽器,RIC 提供了 timeRemaining 方法,讓任務(wù)知道主動(dòng)釋放時(shí)機(jī),我們調(diào)整以上代碼,如下:

          function task(deadline){
            while(true){
             ...
             if(!deadline.timeRemaining()) {
               requestIdleCallback(task);
               // 主動(dòng)退出循環(huán),將控制權(quán)交還瀏覽器
               break;
             }
            };
          }
          requestIdleCallback(task);

          以上示例,可以讓一個(gè)大循環(huán)在“中斷”機(jī)制下,不阻塞瀏覽器的渲染和響應(yīng)。

          注意: RIC 調(diào)用頻率大概是 20 次/秒,遠(yuǎn)遠(yuǎn)低于頁(yè)面流暢度的要求!這樣每次你能得到差不多 50ms 的計(jì)算時(shí)間,如果完全用這 50ms 來(lái)做計(jì)算,同樣會(huì)帶來(lái)交互上的卡頓,所以 React Fiber 是基于自定義一套機(jī)制來(lái)模擬實(shí)現(xiàn)。

          例如:setTimeout、setImmediate、MessageChannel。

          以下是 React Fiber 中的主動(dòng)釋放片段代碼:

          function workLoop(hasTimeRemaining, initialTime{
            let currentTime = initialTime;
            advanceTimers(currentTime);
            currentTask = peek(taskQueue);
            while (
              currentTask !== null &&
              !(enableSchedulerDebugging && isSchedulerPaused)
            ) {
              if (
                currentTask.expirationTime > currentTime &&
                (!hasTimeRemaining || shouldYieldToHost())
              ) {
                // 如果超時(shí),則主動(dòng)退出循環(huán),將控制權(quán)交還瀏覽器
                break;
              }
              ...
            }
            ...
          }

          調(diào)度任務(wù)

          有了中斷機(jī)制,中斷服務(wù)后,不同任務(wù)就能實(shí)現(xiàn)間斷執(zhí)行的可能,如何實(shí)現(xiàn)多任務(wù)的合理調(diào)度,就需要一個(gè)調(diào)度任務(wù)來(lái)進(jìn)行處理,這通常代表著操作系統(tǒng)。例如,當(dāng)一個(gè)任務(wù) A 在執(zhí)行到一半時(shí),被中斷機(jī)制強(qiáng)制中斷,此時(shí)操作系統(tǒng)需要對(duì)當(dāng)前任務(wù) A 進(jìn)行現(xiàn)場(chǎng)保護(hù),如:寄存器數(shù)據(jù),然后切換到下一個(gè)任務(wù) B,當(dāng)任務(wù) A 再次被調(diào)度時(shí),操作系統(tǒng)需要還原之前任務(wù) A 的現(xiàn)場(chǎng)信息,如:寄存器數(shù)據(jù),從而保證任務(wù) A 能繼續(xù)執(zhí)行下一半任務(wù)。調(diào)度過(guò)程中如何保證被中斷任務(wù)的信息不被破壞是一個(gè)非常重要的功能。

          瀏覽器提供的 RIC 機(jī)制,類(lèi)似“中斷服務(wù)”注冊(cè)機(jī)制,注冊(cè)后我們只要合適的時(shí)機(jī)進(jìn)行釋放,就能實(shí)現(xiàn)“中斷”效果,剛也提到對(duì)于不同任務(wù)之間切換,在中斷后,需要考慮現(xiàn)場(chǎng)保護(hù)和現(xiàn)場(chǎng)還原。早期 React 是同步渲染機(jī)制,實(shí)際上是一個(gè)遞歸過(guò)程,遞歸可能會(huì)帶來(lái)長(zhǎng)的調(diào)用棧,這其實(shí)會(huì)給現(xiàn)場(chǎng)保護(hù)和還原變得復(fù)雜,React Fiber 的做法將遞歸過(guò)程拆分成一系列小任務(wù)(Fiber),轉(zhuǎn)換成線性的鏈表結(jié)構(gòu),此時(shí)現(xiàn)場(chǎng)保護(hù)只需要保存下一個(gè)任務(wù)結(jié)構(gòu)信息即可,所以拆分的任務(wù)上需要擴(kuò)展額外信息,該結(jié)構(gòu)記錄著任務(wù)執(zhí)行時(shí)所需要的必備信息:

          {
              stateNode,
              child,
              return,
              sibling,
              expirationTime
              ...
          }

          我們看以下示例代碼:

          ReactDOM.render(
            <div id="A">
              A
              <div id="B">
                B<div id="C">C</div>
              </div>
              <div id="D">D</div>
            </div>
          ,
            node
          );

          當(dāng) React 進(jìn)行渲染時(shí),會(huì)生成如下任務(wù)鏈,此時(shí)如果在執(zhí)行任務(wù) B 后時(shí)發(fā)現(xiàn)時(shí)間不足,主動(dòng)釋放后,只需要記錄下一次任務(wù) C 的信息,等再次調(diào)度時(shí)取得上次記錄的信息即可。使用該機(jī)制后,對(duì)于渲染任務(wù)的優(yōu)先級(jí)、撤銷(xiāo)、掛起、恢復(fù)都能得到非常好的控制。

          總結(jié)

          中斷機(jī)制其實(shí)是一種非常重要的解決資源共享的手段,對(duì)于操作系統(tǒng)而言,它已經(jīng)是一個(gè)必不可少功能。隨著瀏覽器的功能越來(lái)越強(qiáng),越來(lái)越多功能也搬到了瀏覽器,如何保證用戶在使用過(guò)程中的流暢,也是經(jīng)常需要思考的問(wèn)題,在業(yè)務(wù)開(kāi)發(fā)過(guò)程中,我們可以根據(jù)實(shí)際場(chǎng)景利用好“中斷機(jī)制”,提高用戶體驗(yàn)。



          最后


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

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

          點(diǎn)個(gè)在看支持我吧
          瀏覽 91
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  大香蕉AA| 色在线一 | 午夜福利视频一区二区 | 啪啪啪啪啪啪网站 | 色午夜av在线 |