成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

React16性能改善的原理一

zhangqh / 730人閱讀

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

問題背景

React16 更新了底層架構(gòu),新架構(gòu)主要解決更新節(jié)點(diǎn)過多時(shí),頁碼卡頓的問題。譬如如下代碼,根據(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

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

頁面繪制過程

瀏覽器其實(shí)也是類似的原理,每間隔一定的時(shí)間重新繪制一下當(dāng)前頁面。一般來說這個(gè)頻率是每秒60次。也就是說每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è)過程是順序的,如果 JS 執(zhí)行的時(shí)間過長,那么后續(xù)的步驟也就會(huì)被相應(yīng)的延后,導(dǎo)致的后果就是一幀的時(shí)間變長,F(xiàn)PS 變低。人直觀的感受就是頁面變卡頓?;氐缴厦娴睦樱幌伦痈?0000條數(shù)據(jù)導(dǎo)致 React 執(zhí)行了相當(dāng)長的時(shí)間,讓瀏覽器這段時(shí)間內(nèi)無法做其他事情,下一幀被延遲了。

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

setTimeout/setInterval

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

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

太頻繁,導(dǎo)致每一幀的元素變化過大(不是每次改變元素的效果都被顯示出來),表現(xiàn)為動(dòng)畫不順滑。譬如,你期望元素每次移動(dòng)10像素,但是按之前的原理,用戶看到的是元素每次移動(dòng)了40像素。
timeline-too-frequent.png

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

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

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

解決方案

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

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

瀏覽器新API

requestAnimationFrame

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

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

上述代碼執(zhí)行后,在瀏覽器繪制頁面的下一幀重繪前,會(huì)執(zhí)行回調(diào)函數(shù),那么就能保證修改的 dom 的效果能在下一幀被顯示出來。回看上面的幀的生命周期,raf 時(shí)間就是留給 requestAnimationFrame 所注冊的回調(diào)函數(shù)執(zhí)行用的。這樣我們把以前的 setTimeout 動(dòng)畫就可以用 requestAnimationFrame 來改造。

// 舊版:讓元素右移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)畫就流暢了。如果你傳入 requestAnimationFrame 的回調(diào)函數(shù)執(zhí)行的 JS 耗時(shí)過長,一樣會(huì)導(dǎo)致后續(xù)步驟的延時(shí),引起瀏覽器 FPS 的下降。所以這點(diǎn)在寫代碼的時(shí)候要注意。

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

requestIdleCallback

這個(gè)函數(shù)告訴瀏覽器,在空閑時(shí)期依次執(zhí)行注冊的回調(diào)函數(shù)。什么意思呢?上面我們說過瀏覽器在一幀的時(shí)間里面要做這個(gè)事,那個(gè)事,但是并不是每時(shí)每刻這些事情都耗時(shí)的。譬如你打開頁面后什么都不做,那么一幀16毫秒之內(nèi)又沒有啥 JS 需要執(zhí)行又沒有大量的重繪工作,產(chǎn)生了有很多空余時(shí)間??聪聢D,黃色部分就是一幀內(nèi)的空余時(shí)間,當(dāng)瀏覽器發(fā)現(xiàn)一幀有空余時(shí)間就會(huì)看下有沒有調(diào)用 requestIdleCallback 注冊的回調(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í)間了。接下來我們看下基于這個(gè)原理 React 如何優(yōu)化它的更新 dom 的機(jī)制。

React調(diào)度算法

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

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

原來這段寫的匆忙且不好,重新更新了一篇講調(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)的渲染對象
  var prevRenderedElement = prevComponentInstance._currentElement;
  if( shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement) ) {
    // 更新 dom node
    prevComponentInstance.receiveComponent( nextRenderedElement )
  }
}

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

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),對比新老節(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 對上述調(diào)度的實(shí)現(xiàn)基于現(xiàn)實(shí)的考慮進(jìn)行了優(yōu)化:考慮到 1.有的更新是比較緊急的不能等空閑去完成要用 requestAnimationFrame、2.有的是可以放到空閑時(shí)間去執(zhí)行的、3.對于兩個(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)在還沒有把更新真正反應(yīng)到 dom 上的時(shí)候,有來了一次 setState 怎么辦?

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

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101918.html

相關(guān)文章

  • React16性能改善原理(二)

    摘要:接下來我們就是正式的工作了,用循環(huán)從某個(gè)節(jié)點(diǎn)開始遍歷樹。最后一步判斷全局變量是否存在,如果存在則把這次遍歷樹產(chǎn)生的所有更新一次更新到真實(shí)的上去。 前情提要 上一篇我們提到如果 setState 之后,虛擬 dom diff 比較耗時(shí),那么導(dǎo)致瀏覽器 FPS 降低,使得用戶覺得頁面卡頓。那么 react 新的調(diào)度算法就是把原本一次 diff 的過程切分到各個(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é)同前后端開發(fā)人員較多,提高構(gòu)建效率,成為了改善團(tuán)隊(duì)開發(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)目中對做了大量改進(jìn),包括性能上與結(jié)構(gòu)上的改進(jìn)。但通過一些簡化改改良,代碼的可靠性大大提高了。此外,還有周邊的優(yōu)化在目錄下提供一個(gè),用于在舊式中替換。改善,里面內(nèi)置了一個(gè)補(bǔ)丁,也是用于改善性能,或中的性能好差。 本周在支持機(jī)票的項(xiàng)目中對anujs做了大量改進(jìn),包括性能上與結(jié)構(gòu)上的改進(jìn)。與1.1.3一樣,還是差一個(gè)組件就完全兼容阿里的antd UI庫。 框架本身的改進(jìn)有:...

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

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

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

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

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

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

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<