前端性能優(yōu)化方法

一萌狂、瀏覽器渲染過(guò)程

  1. 瀏覽器重繪(Repaint)和回流(Reflow)

重繪(Repaint):當(dāng)頁(yè)面中元素樣式的改變并不影響它在文檔流中的位置時(shí)(例如:color谷丸、background-color、visibility 等)来惧,瀏覽器會(huì)將新樣式賦予給元素并重新繪制它敢订,這個(gè)過(guò)程稱為重繪(Repaint)。

回流(Reflow):當(dāng) Render Tree 中部分或全部元素的尺寸描滔、結(jié)構(gòu)、或某些屬性發(fā)生改變時(shí)踪古,瀏覽器重新渲染部分或全部文檔的過(guò)程稱為回流含长。

回流必將引起重繪,重繪不一定會(huì)引起回流伏穆。

會(huì)導(dǎo)致回流的操作:

  • 頁(yè)面首次渲染
  • 瀏覽器窗口大小發(fā)生改變
  • 元素尺寸或位置發(fā)生改變?cè)貎?nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)
  • 元素字體大小變化
  • 添加或者刪除可見(jiàn)的 DOM 元素
  • 激活 CSS 偽類(例如:hover)
  • 查詢某些屬性或調(diào)用某些方法
  • 一些常用且會(huì)導(dǎo)致回流的屬性和方法
    clientWidth拘泞、clientHeight、clientTop枕扫、clientLeftoffsetWidth陪腌、offsetHeight、offsetTop、offsetLeftscrollWidth偷厦、scrollHeight商叹、scrollTop、scrollLeftscrollIntoView()只泼、scrollIntoViewIfNeeded()、getComputedStyle()卵洗、
    getBoundingClientRect()请唱、scrollTo()
    性能影響
    回流比重繪的代價(jià)要更高。
    有時(shí)即使僅僅回流一個(gè)單一的元素过蹂,它的父元素以及任何跟隨它的元素也會(huì)產(chǎn)生回流∈螅現(xiàn)代瀏覽器會(huì)對(duì)頻繁的回流或重繪操作進(jìn)行優(yōu)化:瀏覽器會(huì)維護(hù)一個(gè)隊(duì)列,把所有引起回流和重繪的操作放入隊(duì)列中酷勺,如果隊(duì)列中的任務(wù)數(shù)量或者時(shí)間間隔達(dá)到一個(gè)閾值的本橙,瀏覽器就會(huì)將隊(duì)列清空,進(jìn)行一次批處理脆诉,這樣可以把多次回流和重繪變成一次甚亭。

使用 transform 和 opacity 屬性更改來(lái)實(shí)現(xiàn)動(dòng)畫
在 CSS 中,transforms 和 opacity 這兩個(gè)屬性更改不會(huì)觸發(fā)重排與重繪击胜,它們是可以由合成器(composite)單獨(dú)處理的屬性亏狰。

二、網(wǎng)頁(yè)交互

  1. 防抖(debounce)/節(jié)流(throttle)
    防抖是控制次數(shù)偶摔,節(jié)流時(shí)控制頻率暇唾,如果事件一直觸發(fā),則防抖下函數(shù)在中間不會(huì)執(zhí)行(最后執(zhí)行一次)辰斋,節(jié)流下函數(shù)按照一定的頻率執(zhí)行策州。

debounce:在第一次觸發(fā)事件時(shí),不立即執(zhí)行函數(shù)宫仗,而是給出一個(gè)期限值比如200ms够挂,然后:
如果在200ms內(nèi)沒(méi)有再次觸發(fā)事件,那么就執(zhí)行函數(shù)
如果在200ms內(nèi)再次觸發(fā)事件锰什,那么當(dāng)前的計(jì)時(shí)取消下硕,重新開(kāi)始計(jì)時(shí)

效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件,只會(huì)執(zhí)行一次函數(shù)汁胆。
實(shí)現(xiàn):

function debounce(fn,delay){
  let timer  = null;
  return function () {
    if(timer){
      clearTimeout(timer);
    }
    timer = setTimeout(fn,delay);
  }
}

定義:對(duì)于短時(shí)間內(nèi)連續(xù)觸發(fā)的事件(如滾動(dòng)事件)梭姓,在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件嫩码,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間


節(jié)流:讓函數(shù)執(zhí)行一次后誉尖,在某個(gè)時(shí)間段內(nèi)暫時(shí)失效,過(guò)了這段時(shí)間后再重新激活

效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件铸题,那么在函數(shù)執(zhí)行一次之后铡恕,該函數(shù)在指定的時(shí)間期限內(nèi)不再工作琢感,直至過(guò)了這段時(shí)間才重新生效。

實(shí)現(xiàn):

funtion throttler(fn, delay) {
  let valid = true;
  return function () {
    if(!valid) {
      return false;
     }
    valid = false;
    setTimeout( () => {
      fn();
      valid = true;
    },delay)
   }
}
/* 請(qǐng)注意探熔,節(jié)流函數(shù)并不止上面這種實(shí)現(xiàn)方案,
   例如可以完全不借助setTimeout驹针,可以把狀態(tài)位換成時(shí)間戳,然后利用時(shí)間戳差值是否大于指定間隔時(shí)間來(lái)做判定诀艰。
   也可以直接將setTimeout的返回的標(biāo)記當(dāng)做判斷條件-判斷當(dāng)前定時(shí)器是否存在柬甥,如果存在表示還在冷卻,并且在執(zhí)行fn之后消除定時(shí)器表示激活其垄,原理都一樣
    */

定義:對(duì)于短時(shí)間內(nèi)連續(xù)觸發(fā)的事件苛蒲,函數(shù)執(zhí)行一次后會(huì)暫時(shí)失效,過(guò)了n秒后才能再次生效绿满。

三臂外、React性能優(yōu)化

三(一)React渲染優(yōu)化

props的改變(被動(dòng)渲染),和state改變(主動(dòng)渲染)會(huì)導(dǎo)致react進(jìn)行重新渲染(進(jìn)行dom diff)

1. 綁定事件盡量不要使用箭頭函數(shù)喇颁,即不要在jsx內(nèi)寫內(nèi)聯(lián)函數(shù)
<Child handerClick={() => { this.handleClick() }}/>

原因有:每一次渲染更新這段jsx都會(huì)產(chǎn)生新的函數(shù)對(duì)象(即使直接綁定的DOM元素)漏健;
Child 會(huì)因?yàn)檫@個(gè)新生成的箭頭函數(shù)而進(jìn)行更新,每一次的props都是新的无牵,在未給Child任何特殊更新限定條件的時(shí)候,從而產(chǎn)生Child 組件的不必要渲染

解決問(wèn)題:類組件(有狀態(tài)組件)綁定事件直接指向類中的函數(shù)漾肮;函數(shù)組件(無(wú)狀態(tài)組件)函數(shù)定義使用useMemo/useCallback
有狀態(tài)組件

class index extends React.Component{
    handerClick=()=>{
        console.log(666)
    }
    handerClick1=()=>{
        console.log(777)
    }
    render(){
        return <div>
            <ChildComponent handerClick={ this.handerClick }  />
            <div onClick={ this.handerClick1 }  >hello,world</div>
        </div>
    }
}

無(wú)狀態(tài)組件

function index(){
    const handerClick1 = useMemo(()=>()=>{
       console.log(777)
    },[])  /* [] 存在當(dāng)前 handerClick1 的依賴項(xiàng)*/
    const handerClick = useCallback(()=>{ console.log(666) },[])  /* [] 存在當(dāng)前 handerClick 的依賴項(xiàng)*/
    return <div>
        <ChildComponent handerClick={ handerClick }  />
        <div onClick={ handerClick1 }  >hello,world</div>
    </div>
}
2. 不要使用inline定義的方法或Object為props傳值 (傳遞給子組件的固定對(duì)象提前定義好一個(gè)變量)
// 每一次渲染都會(huì)產(chǎn)生一個(gè)對(duì)象給style
//  都會(huì)被認(rèn)為是一個(gè)style這個(gè)prop發(fā)生了變化,因?yàn)槭且粋€(gè)新的對(duì)象
<Foo style={{ color:"red" }}

// 對(duì)象提前定義
const fooStyle = { color: "red" }; // 可以放在構(gòu)造函數(shù)中或者函數(shù)組件外面
<Foo style={fooStyle} />
3. 循環(huán)/列表正確使用key

key目的就是在一次循環(huán)中茎毁,找到與新節(jié)點(diǎn)對(duì)應(yīng)的老節(jié)點(diǎn)克懊,復(fù)用節(jié)點(diǎn),節(jié)省開(kāi)銷
增加key后七蜘,React就不是diff谭溉,而是直接使用insertBefore操作移動(dòng)組件位置,而這個(gè)操作是移動(dòng)DOM節(jié)點(diǎn)最高效的辦法
在選取Key值時(shí)盡量不要用索引號(hào)橡卤,因?yàn)槿绻?dāng)數(shù)據(jù)的添加方式不是順序添加扮念,而是以其他方式(逆序,隨機(jī)等)碧库,會(huì)導(dǎo)致每一次添加數(shù)據(jù)削锰,每一個(gè)數(shù)據(jù)值的索引號(hào)都不一樣廷支,這就導(dǎo)致了Key的變化拣凹,而當(dāng)Key變化時(shí)础浮,React就會(huì)認(rèn)為這與之前的數(shù)據(jù)值不相同,會(huì)多次執(zhí)行渲染沽瞭,會(huì)造成大量的性能浪費(fèi)

4. 組件盡可能的細(xì)分迁匠,比如一個(gè)input+list組件,可以將list分成一個(gè)PureComponent,只在list數(shù)據(jù)變化時(shí)更新城丧。否則在input值變化頁(yè)面重新渲染的時(shí)候延曙,list也需要進(jìn)行不必要的DOM diff

當(dāng)一個(gè)組件的props或者state改變時(shí),React通過(guò)比較新返回的元素和之前渲染的元素來(lái)決定是否有必要更新實(shí)際的DOM亡哄。當(dāng)他們不相等時(shí)枝缔,React會(huì)更新DOM。

在一些情況下蚊惯,你的組件可以通過(guò)重寫這個(gè)生命周期函數(shù)shouldComponentUpdate來(lái)提升速度魂仍, 它是在重新渲染過(guò)程開(kāi)始前觸發(fā)的。 這個(gè)函數(shù)默認(rèn)返回true拣挪,可使React執(zhí)行更新。

5. 減少不必要的props引起的重繪

在 class Component 中可以使用shouldComponentUpdate或者PureComponent減少重復(fù)渲染俱诸。
Hooks組件則可以使用React.memo
普通的 React.memo和PureComponent很像,對(duì)props做一層淺比較菠劝,如果沒(méi)發(fā)生變更則不執(zhí)行重繪

const Child = ()=><span>test</span>
export default React.memo(Child);

React.memo支持第二個(gè)參數(shù)compare, 返回 true 時(shí),不會(huì)觸發(fā)更新睁搭,來(lái)手動(dòng)判斷是否需要渲染赶诊。例如對(duì)于一個(gè)普通受控組件,當(dāng)defaultValue發(fā)生變更時(shí)無(wú)需重繪組件园骆,所以可以用下面的代碼實(shí)現(xiàn)舔痪。

function MyComponent({ defaultValue, value, onChange }) {
  return null;
}

function compare(prevProps, props) {
  return prevProps.value === props.value && 
         prevProps.onChange === props.onChange;
}

export default React.memo(MyComponent, compare);
6. 減少不必要state引起的重繪

useState的一條最佳實(shí)踐是將state盡可能的顆粒化锌唾,但是在異步回調(diào)中同時(shí)更新多個(gè)狀態(tài)時(shí)會(huì)觸發(fā)多次渲染(在 react 的 event handler 內(nèi)部同步的多次 setState 會(huì)被 batch 為一次更新锄码,但是在一個(gè)異步的事件循環(huán)里面多次 setState,react 不會(huì) batch晌涕。可以使用 ReactDOM.unstable_batchedUpdates 來(lái)強(qiáng)制 batch)

import { unstable_batchUpdateds } from 'react-dom'滋捶;
function Home() {
     const [userInfo, setUserInfo] = useState(null);
     const [loading, setLoading] = useState(true);
   
     useEffect(() => {
         asyncFetchUser().then(() => {
// ------------------
         unstable_batchUpdateds(()=>{
             setUserInfo({});
             setLoading(false);
         })
// -----------------
         });
     }, []);

     console.log('render');
     return null;
 }
7. 善用useMemo/useCallback/React.memo

對(duì)于無(wú)狀態(tài)組件,數(shù)據(jù)更新就等于函數(shù)上下文的重復(fù)執(zhí)行余黎。那么函數(shù)里面的變量重窟,方法就會(huì)重新聲明,可以用useMemo/useCallback將常量或者函數(shù)緩存下來(lái)避免重復(fù)執(zhí)行

8. 合理使用狀態(tài)管理(dva/redux-sage/...)

狀態(tài)管理的主要作用:一 就是解決跨層級(jí)組件通信問(wèn)題 。二 就是對(duì)一些全局公共狀態(tài)的緩存惧财。
自身組件單獨(dú)的數(shù)據(jù)可直接再組件內(nèi)部請(qǐng)求數(shù)據(jù)巡扇,避免數(shù)據(jù)走了一遍狀態(tài)管理,最終還是回到了組件本身的情況

三(二)React獲取數(shù)據(jù)優(yōu)化

使用reselect庫(kù):
在使用Redux進(jìn)行數(shù)據(jù)的傳遞時(shí),特別是經(jīng)常有重復(fù)性的數(shù)據(jù)傳遞操作時(shí)垮衷,可以使用reselect庫(kù)在內(nèi)部對(duì)數(shù)據(jù)進(jìn)行緩存處理厅翔,在重復(fù)調(diào)用時(shí)便可使用緩存快速加載,加強(qiáng)性能

React hooks 對(duì)比class優(yōu)點(diǎn)

  1. Hooks組件復(fù)用邏輯相比高階組件復(fù)用邏輯更易維護(hù)
    比如componentDidMount/shouldComponentUpdate中帘靡,可能就會(huì)有大量邏輯代碼知给,包括網(wǎng)絡(luò)請(qǐng)求,一些事件的監(jiān)聽(tīng),hooks的好處: 面向生命周期編程變成了面向業(yè)務(wù)邏輯編程涩赢,便于維護(hù)
  2. hook可以將state細(xì)微化戈次,便于組件的拆分, class時(shí)期筒扒,setState后需要對(duì)比整個(gè)虛擬dom的狀態(tài)怯邪,對(duì)一個(gè)復(fù)雜頁(yè)面,幾十個(gè)狀態(tài)需要對(duì)比耗費(fèi)性能花墩。而hook階段只需要對(duì)比一個(gè)值即可悬秉,性能更佳。

用hook代替高階組件

  1. useSelector/useDispatch代替高階函數(shù)connect
import { useDispatch, useSelector } from 'react-redux';
//....
 const dispatch = useDispatch();
useEffect (()=>{
  dispatch({
    type:'', // ...
  })
},[])
// ....
 const { progressList, loading } = useSelector(
    ({
      dataManage,
      loading,
    }: {
      dataManage: any;
      loading: any;
    }) => ({
      ...dataManage,
      loading: loading.effects['dataManage/fetchProgressList'],
    }),
  );
return .....
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冰蘑,一起剝皮案震驚了整個(gè)濱河市和泌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祠肥,老刑警劉巖武氓,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仇箱,居然都是意外死亡县恕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門剂桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忠烛,“玉大人,你說(shuō)我怎么就攤上這事权逗∶朗” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵旬迹,是天一觀的道長(zhǎng)火惊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奔垦,這世上最難降的妖魔是什么屹耐? 我笑而不...
    開(kāi)封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮椿猎,結(jié)果婚禮上惶岭,老公的妹妹穿的比我還像新娘。我一直安慰自己犯眠,他們只是感情好按灶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著筐咧,像睡著了一般鸯旁。 火紅的嫁衣襯著肌膚如雪噪矛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天铺罢,我揣著相機(jī)與錄音艇挨,去河邊找鬼。 笑死韭赘,一個(gè)胖子當(dāng)著我的面吹牛缩滨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泉瞻,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脉漏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了袖牙?” 一聲冷哼從身側(cè)響起侧巨,我...
    開(kāi)封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鞭达,沒(méi)想到半個(gè)月后刃泡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碉怔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了禁添。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撮胧。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖老翘,靈堂內(nèi)的尸體忽然破棺而出芹啥,到底是詐尸還是另有隱情,我是刑警寧澤铺峭,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布墓怀,位于F島的核電站,受9級(jí)特大地震影響卫键,放射性物質(zhì)發(fā)生泄漏傀履。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一莉炉、第九天 我趴在偏房一處隱蔽的房頂上張望钓账。 院中可真熱鬧,春花似錦絮宁、人聲如沸梆暮。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)啦粹。三九已至偿荷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唠椭,已是汗流浹背跳纳。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泪蔫,地道東北人棒旗。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像撩荣,于是被迫代替她去往敵國(guó)和親铣揉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 一餐曹、頁(yè)面加載及渲染過(guò)程優(yōu)化 CRP(關(guān)鍵渲染路徑Critical Rendering Path): 關(guān)鍵渲染路徑是...
    小可愛(ài)多_bdb1閱讀 216評(píng)論 0 0
  • 什么是前端性能優(yōu)化(what)逛拱? 從用戶訪問(wèn)資源到資源完整的展現(xiàn)在用戶面前的過(guò)程中,通過(guò)技術(shù)手段和優(yōu)化策略台猴,縮短每...
    我性本傲閱讀 3,494評(píng)論 0 3
  • 網(wǎng)絡(luò)請(qǐng)求 減少HTTP資源請(qǐng)求次數(shù)(合并接口) 減小HTTP請(qǐng)求大小 避免頁(yè)面中空的href和src 減少頁(yè)面重定...
    xiaolizhenzhen閱讀 452評(píng)論 0 0
  • 網(wǎng)絡(luò)請(qǐng)求 減少HTTP資源請(qǐng)求次數(shù)(合并接口) 減小HTTP請(qǐng)求大小 避免頁(yè)面中空的href和src 減少頁(yè)面重定...
    Artifacts閱讀 250評(píng)論 0 0
  • 日常工作和生活中朽合,我們經(jīng)常利用瀏覽器去打開(kāi)一些URL來(lái)獲取我們所需的資源,那么作為一個(gè)開(kāi)發(fā)者或者性能測(cè)試工程師饱狂,如...
    七月鎏金閱讀 710評(píng)論 0 0