成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

React16性能改善的原理一

zhangqh / 755人閱讀

摘要:接下來(lái)看下偽代碼調(diào)度算法偽代碼原來(lái)這段寫(xiě)的匆忙且不好,重新更新了一篇講調(diào)度算法的大概實(shí)現(xiàn)性能改善的原理二。

問(wèn)題背景

React16 更新了底層架構(gòu),新架構(gòu)主要解決更新節(jié)點(diǎn)過(guò)多時(shí),頁(yè)碼卡頓的問(wèn)題。譬如如下代碼,根據(jù)用戶輸入的文字生成10000行數(shù)據(jù),用戶輸入框會(huì)出現(xiàn)卡頓現(xiàn)象。

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      rowData: []
    }
  }

  handleUserInput = (e)=>{
    let userInput = e.target.value;
    let newRowData = [];
    for( let i = 0; i < 10000; i++) {
      newRowData.push( userInput );
    }
    this.setState( {
      rowData: newRowData
    } )
  }

  renderRows() {
    return this.rowData.map( (s,index)=>{
      return (
        
          {s}
        
      )
    } )
  }

  render() {
    return (
      
{ this.renderRows() }
); } }
卡頓的原因 FPS

為了引出瀏覽器卡頓真正的原因,我們先簡(jiǎn)單介紹一個(gè)概念:FPS(Frames Per Second) - 每秒傳輸幀數(shù)。舉個(gè)例子,一般來(lái)說(shuō)動(dòng)畫(huà)片是如何動(dòng)起來(lái)的呢?是以極快的速度連續(xù)播放靜態(tài)的圖片,利用視網(wǎng)膜圖像殘留效應(yīng),讓人產(chǎn)生動(dòng)起來(lái)的錯(cuò)覺(jué)。那么這個(gè)播放要多塊呢?每秒最少要展示24張圖片,觀眾才勉強(qiáng)不會(huì)感受到畫(huà)面延時(shí)(即 FPS 達(dá)到24,不會(huì)讓人覺(jué)得卡頓)。

頁(yè)面繪制過(guò)程

瀏覽器其實(shí)也是類(lèi)似的原理,每間隔一定的時(shí)間重新繪制一下當(dāng)前頁(yè)面。一般來(lái)說(shuō)這個(gè)頻率是每秒60次。也就是說(shuō)每16毫秒( 1 / 60 ≈ 0.0167 )瀏覽器會(huì)有一個(gè)周期性地重繪行為,這每16毫秒我們稱為一幀。這一幀的時(shí)間里面瀏覽器做些什么事情呢:

執(zhí)行JS。

計(jì)算Style。

構(gòu)建布局模型(Layout)。

繪制圖層樣式(Paint)。

組合計(jì)算渲染呈現(xiàn)結(jié)果(Composite)。

inter-frame idle period.jpg

這個(gè)過(guò)程是順序的,如果 JS 執(zhí)行的時(shí)間過(guò)長(zhǎng),那么后續(xù)的步驟也就會(huì)被相應(yīng)的延后,導(dǎo)致的后果就是一幀的時(shí)間變長(zhǎng),F(xiàn)PS 變低。人直觀的感受就是頁(yè)面變卡頓?;氐缴厦娴睦?,一下子更新10000條數(shù)據(jù)導(dǎo)致 React 執(zhí)行了相當(dāng)長(zhǎng)的時(shí)間,讓瀏覽器這段時(shí)間內(nèi)無(wú)法做其他事情,下一幀被延遲了。

有人會(huì)想到說(shuō),誒,一次執(zhí)行時(shí)間太長(zhǎng)會(huì)卡我能理解,但是為啥我以前用定時(shí)器做 JS 動(dòng)畫(huà)有時(shí)也會(huì)卡呢?下面我們就分析下原因。

setTimeout/setInterval

我們把 setTimeout 和瀏覽器幀流兩條時(shí)間線放在一起看一下( 綠色是 paint,紫色是 render,黃色是執(zhí)行 JS ):

第一種完美的情況,就是 setTimeout 執(zhí)行的頻率和瀏覽器的幀率相同。
timeline-perfect-frequency.png

太頻繁,導(dǎo)致每一幀的元素變化過(guò)大(不是每次改變?cè)氐男Ч急伙@示出來(lái)),表現(xiàn)為動(dòng)畫(huà)不順滑。譬如,你期望元素每次移動(dòng)10像素,但是按之前的原理,用戶看到的是元素每次移動(dòng)了40像素。
timeline-too-frequent.png

setTimeout 的頻率低于瀏覽器默認(rèn)幀率,導(dǎo)致跳幀,表現(xiàn)也是不順滑。這個(gè)就不用說(shuō)了,元素可能幾幀才動(dòng)一次。
timeline-skip-frame.png

setTimeout 某次或者每次執(zhí)行的函數(shù)時(shí)間過(guò)長(zhǎng),導(dǎo)致瀏覽器的 FPS 降低,表現(xiàn)為動(dòng)畫(huà)卡頓。這種別說(shuō)動(dòng)畫(huà)卡,頁(yè)面也卡了。
timeline-delay.png

想象一下,當(dāng)你不知道瀏覽器頁(yè)面繪制原理的時(shí)候是不是全憑感覺(jué)來(lái)設(shè)置 setTimeout 的間隔?當(dāng)然你也可以把 setTimeout 的間隔設(shè)置成16毫秒。不過(guò)如果對(duì) event loop 機(jī)制了解的話,你會(huì)知道這個(gè)只能大致保證按這個(gè)時(shí)間間隔執(zhí)行,并不會(huì)嚴(yán)格保證。setInterval 也是類(lèi)似,但是比 setTimeout 更不可控。

解決方案

回過(guò)頭來(lái)我們仔細(xì)分解下每一幀瀏覽器要做些什么(見(jiàn)下圖),先是響應(yīng)各種事件,然后執(zhí)行 event loop 中的任務(wù),然后是一段 raf 時(shí)間,最后是計(jì)算排版(layout)和重新繪制(paint)。大致你可以認(rèn)為是先執(zhí)行程序,然后再根據(jù) JS 執(zhí)行的結(jié)果重繪頁(yè)面,當(dāng)然如果 dom 元素沒(méi)有任何變化,那么重繪這個(gè)步驟就省了。
life of a frame.png

如果我們能保證 JS 動(dòng)畫(huà)的每次執(zhí)行都在重繪前,那么我們就能做到動(dòng)畫(huà)的順滑,setTimeout 無(wú)法保證,但是瀏覽器提供了新的 API 來(lái)幫助我們了。

瀏覽器新API

requestAnimationFrame

這個(gè)函數(shù)的作用就是告訴瀏覽器你希望執(zhí)行一段 JS,并且要求瀏覽器在下次重繪之前調(diào)用這段 JS 所在的回調(diào)函數(shù)。

requestAnimationFrame( function(){
  document.body.style.width = "100px";
} )

上述代碼執(zhí)行后,在瀏覽器繪制頁(yè)面的下一幀重繪前,會(huì)執(zhí)行回調(diào)函數(shù),那么就能保證修改的 dom 的效果能在下一幀被顯示出來(lái)?;乜瓷厦娴膸纳芷?,raf 時(shí)間就是留給 requestAnimationFrame 所注冊(cè)的回調(diào)函數(shù)執(zhí)行用的。這樣我們把以前的 setTimeout 動(dòng)畫(huà)就可以用 requestAnimationFrame 來(lái)改造。

// 舊版:讓元素右移500像素
function moveToRight( div ) {
  let left = parseInt( div.style.left );
  if ( left < 500 ) {
    div.style.left = (left+10+"px");
    setTimeout( function(){
      moveToRight( div );
    }, 16 )
  } else {
    return;
  }
}
moveToRight( div );

// 新版:讓元素右移500像素
function moveToRight( div ) {
  let left = parseInt( div.style.left );
  if ( left < 500 ) {
    div.style.left = (left+10+"px");
    requestAnimationFrame( function(){
      moveToRight( div );
    } )
  } else {
    return;
  }
}

requestAnimationFrame( function(){
  moveToRight( div );
} )

特別注意:不是用了 requestAnimationFrame 后動(dòng)畫(huà)就流暢了。如果你傳入 requestAnimationFrame 的回調(diào)函數(shù)執(zhí)行的 JS 耗時(shí)過(guò)長(zhǎng),一樣會(huì)導(dǎo)致后續(xù)步驟的延時(shí),引起瀏覽器 FPS 的下降。所以這點(diǎn)在寫(xiě)代碼的時(shí)候要注意。

現(xiàn)在有一個(gè)問(wèn)題,傳入 requestAnimationFrame 的回調(diào)函數(shù)一定是會(huì)被被安排在下一次重繪前所調(diào)用的,但是如果 raf 時(shí)間之前就已經(jīng)執(zhí)行了長(zhǎng)時(shí)間的 JS,那么我再執(zhí)行這個(gè)回調(diào)豈不是雪上加霜?我能不能要求這種情況說(shuō),我的代碼也不是很緊急,判斷下如果當(dāng)前幀不“忙”,我就執(zhí)行,如果幀“忙”,我可以等下一幀之類(lèi)的呢?好!下一個(gè) API 來(lái)了。

requestIdleCallback

這個(gè)函數(shù)告訴瀏覽器,在空閑時(shí)期依次執(zhí)行注冊(cè)的回調(diào)函數(shù)。什么意思呢?上面我們說(shuō)過(guò)瀏覽器在一幀的時(shí)間里面要做這個(gè)事,那個(gè)事,但是并不是每時(shí)每刻這些事情都耗時(shí)的。譬如你打開(kāi)頁(yè)面后什么都不做,那么一幀16毫秒之內(nèi)又沒(méi)有啥 JS 需要執(zhí)行又沒(méi)有大量的重繪工作,產(chǎn)生了有很多空余時(shí)間??聪聢D,黃色部分就是一幀內(nèi)的空余時(shí)間,當(dāng)瀏覽器發(fā)現(xiàn)一幀有空余時(shí)間就會(huì)看下有沒(méi)有調(diào)用 requestIdleCallback 注冊(cè)的回調(diào)函數(shù),有的話就執(zhí)行下。如果執(zhí)行某個(gè)回調(diào)前看到幀結(jié)束了,那么就等下一次有空閑時(shí)間接著執(zhí)行剩余的回調(diào)函數(shù)。

inter-frame idle period.jpg

有了 requestAnimationFrame 和 requestIdleCallback 我們就能比以前更細(xì)粒度的控制 JS 執(zhí)行的時(shí)間了。接下來(lái)我們看下基于這個(gè)原理 React 如何優(yōu)化它的更新 dom 的機(jī)制。

React調(diào)度算法

React 代碼中如果某處 setState 被調(diào)用引起了一系列更新,React 大致要做的是生成新的虛擬 dom 樹(shù),然后和老的虛擬 dom 樹(shù)做比較,生成更新列表,最后根據(jù)這個(gè)列表更新真實(shí)的 dom。當(dāng)然更新 dom 耗時(shí)在 JS 層面現(xiàn)階段是沒(méi)法優(yōu)化了,而生成虛擬 dom,做新老虛擬 dom 比較過(guò)程的耗時(shí),是可能隨著應(yīng)用的復(fù)雜程度而增加的。React16 之前絕大多數(shù)情況是一次完成虛擬 dom 到真實(shí) dom 更新的整個(gè)過(guò)程的。那么這個(gè)過(guò)程如果在一幀里面耗時(shí)過(guò)長(zhǎng),頁(yè)面就卡頓了。React16 的思路就是想利用 requestAnimationFrame 和 requestIdleCallback 兩個(gè)新 API,把一次耗時(shí)較長(zhǎng)的更新任務(wù)分解到多個(gè)幀去執(zhí)行。這樣給瀏覽器留出時(shí)間去響應(yīng)頁(yè)面上的其他事件,解決卡頓的問(wèn)題。接下來(lái)看下偽代碼:

調(diào)度算法偽代碼

原來(lái)這段寫(xiě)的匆忙且不好,重新更新了一篇講調(diào)度算法的大概實(shí)現(xiàn)React16性能改善的原理(二)。

原更新步驟大致為

// 原更新步驟大致為:
setState( partialState ) {
  var inst = this._instance;
  var nextState = Object.assign( {}, inst.state, partialState );
  // 根據(jù)新的 state 生成新的虛擬 dom
  inst.state = nextState;
  var nextRenderedElement = inst.render();
  // 獲取上一次的虛擬 dom
  var prevComponentInstance = this._renderedComponent; // render 中的根節(jié)點(diǎn)的渲染對(duì)象
  var prevRenderedElement = prevComponentInstance._currentElement;
  if( shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement) ) {
    // 更新 dom node
    prevComponentInstance.receiveComponent( nextRenderedElement )
  }
}

根據(jù)新的優(yōu)化思路,React16新的更新過(guò)長(zhǎng)大致為:

setState( partialState ) {
  updateQueue.push( {
    instance: this,
    partialState: partialState
  } );
  requestIdleCallback( doDiff )
}

function doDiff( deadline ) {
  let nextUpdate = updateQueue.shift();
  let pendingCommit = [];
  // 如果更新隊(duì)列里面有更新,且時(shí)間富裕,則逐步計(jì)算出需要更新的內(nèi)容
  while( nextUpdate && deadline.timeRemaining()>ENOUGH_TIME ) {
    // 生成 fiber 節(jié)點(diǎn),對(duì)比新老節(jié)點(diǎn),生成更新dom的任務(wù)
    pendingCommit.push( calculateDomModification(nextUpdate) ); // 把更新 dom 的任務(wù)加入待更新隊(duì)列
    nextUpdate = updateQueue.shift();
  }
  // 一次把當(dāng)前時(shí)間片所有的 diff 出的更新任務(wù)都更新到 dom 上
  if ( pendingCommit.lengt>0 ) {
    commitAllWork( pendingCommit );
  }

  // 如果更新隊(duì)列還有更新,但是時(shí)間片耗盡了,那么在下次空閑時(shí)間再更新
  if ( nextUnitOfWork || updateQueue.length > 0 ) {
    requestIdleCallback( doDiff );
  }
}

實(shí)際代碼當(dāng)然要比這個(gè)復(fù)雜的多,React 對(duì)上述調(diào)度的實(shí)現(xiàn)基于現(xiàn)實(shí)的考慮進(jìn)行了優(yōu)化:考慮到 1.有的更新是比較緊急的不能等空閑去完成要用 requestAnimationFrame、2.有的是可以放到空閑時(shí)間去執(zhí)行的、3.對(duì)于兩個(gè)新 API 的瀏覽器支持不是很好、4.瀏覽器默認(rèn)刷新頻率的的時(shí)間片太短。React 團(tuán)隊(duì)實(shí)現(xiàn)了一個(gè)自己的調(diào)度函數(shù) requestAnimationFrameWithTimeout。

其他關(guān)注點(diǎn)

后續(xù)還打算更新其他細(xì)節(jié)的內(nèi)容,等研究好了再更新,譬如:
1. 更新任務(wù)不是同步完成的,如果同一個(gè)節(jié)點(diǎn)在還沒(méi)有把更新真正反應(yīng)到 dom 上的時(shí)候,有來(lái)了一次 setState 怎么辦?

2. React fiber 為什么是鏈?zhǔn)浇Y(jié)構(gòu)?

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101918.html

相關(guān)文章

  • React16性能改善原理(二)

    摘要:接下來(lái)我們就是正式的工作了,用循環(huán)從某個(gè)節(jié)點(diǎn)開(kāi)始遍歷樹(shù)。最后一步判斷全局變量是否存在,如果存在則把這次遍歷樹(shù)產(chǎn)生的所有更新一次更新到真實(shí)的上去。 前情提要 上一篇我們提到如果 setState 之后,虛擬 dom diff 比較耗時(shí),那么導(dǎo)致瀏覽器 FPS 降低,使得用戶覺(jué)得頁(yè)面卡頓。那么 react 新的調(diào)度算法就是把原本一次 diff 的過(guò)程切分到各個(gè)幀去執(zhí)行,使得瀏覽器在 dif...

    guqiu 評(píng)論0 收藏0
  • webpack打包分析與性能優(yōu)化

    摘要:打包分析與性能優(yōu)化背景在去年年末參與的一個(gè)項(xiàng)目中,項(xiàng)目技術(shù)棧使用,生產(chǎn)環(huán)境全量構(gòu)建將近三分鐘,項(xiàng)目業(yè)務(wù)模塊多達(dá)數(shù)百個(gè),項(xiàng)目依賴數(shù)千個(gè),并且該項(xiàng)目協(xié)同前后端開(kāi)發(fā)人員較多,提高構(gòu)建效率,成為了改善團(tuán)隊(duì)開(kāi)發(fā)效率的關(guān)鍵之一。 webpack打包分析與性能優(yōu)化 背景 在去年年末參與的一個(gè)項(xiàng)目中,項(xiàng)目技術(shù)棧使用react+es6+ant-design+webpack+babel,生產(chǎn)環(huán)境全量構(gòu)建將...

    joy968 評(píng)論0 收藏0
  • 性能迷你React框架anujs1.1.4發(fā)布

    摘要:本周在支持機(jī)票的項(xiàng)目中對(duì)做了大量改進(jìn),包括性能上與結(jié)構(gòu)上的改進(jìn)。但通過(guò)一些簡(jiǎn)化改改良,代碼的可靠性大大提高了。此外,還有周邊的優(yōu)化在目錄下提供一個(gè),用于在舊式中替換。改善,里面內(nèi)置了一個(gè)補(bǔ)丁,也是用于改善性能,或中的性能好差。 本周在支持機(jī)票的項(xiàng)目中對(duì)anujs做了大量改進(jìn),包括性能上與結(jié)構(gòu)上的改進(jìn)。與1.1.3一樣,還是差一個(gè)組件就完全兼容阿里的antd UI庫(kù)。 框架本身的改進(jìn)有:...

    elva 評(píng)論0 收藏0
  • [譯] 唯快不破:Web 應(yīng)用 13 個(gè)優(yōu)化步驟

    摘要:譯文地址譯唯快不破應(yīng)用的個(gè)優(yōu)化步驟前端的逆襲知乎專欄原文地址時(shí)過(guò)境遷,應(yīng)用比以往任何時(shí)候都更具交互性。使用負(fù)載均衡方案我們?cè)谥坝懻摼彺娴臅r(shí)候簡(jiǎn)要提到了內(nèi)容分發(fā)網(wǎng)絡(luò)。換句話說(shuō),元素的串形訪問(wèn)會(huì)削弱負(fù)載均衡器以最佳形式 歡迎關(guān)注知乎專欄 —— 前端的逆襲歡迎關(guān)注我的博客,知乎,GitHub。 譯文地址:【譯】唯快不破:Web 應(yīng)用的 13 個(gè)優(yōu)化步驟 - 前端的逆襲 - 知乎專欄原文地...

    haobowd 評(píng)論0 收藏0
  • 性能迷你React框架 anu1.2.1 發(fā)布

    摘要:這次更新主要是改善了對(duì)焦點(diǎn)的處理及的語(yǔ)法糖的支持優(yōu)化的性能,將原方法內(nèi)部用到函數(shù)與對(duì)象提到全局上來(lái),這就比官方的對(duì)象池技術(shù)更能提升性能。 anu1.2.1這次更新主要是改善了對(duì)焦點(diǎn)的處理及react16.2的Fragment語(yǔ)法糖的支持 優(yōu)化fiberizeChildren的性能,將原方法內(nèi)部用到函數(shù)與對(duì)象提到全局上來(lái),這就比官方的對(duì)象池技術(shù)更能提升性能。 修復(fù)受控組件在textar...

    phpmatt 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<