前端性能優化全方位解讀 【更新至React】

性能優化是一門大學問,本文僅對我的一些積累知識的闡述,歡迎下面補充。

拋出一個問題,從輸入url地址欄到全部內容顯示到界面上作了哪些事?css

  • 1.瀏覽器向DNS服務器請求解析該 URL 中的域名所對應的 IP 地址;
  • 2.創建TCP鏈接(三次握手);
  • 3.瀏覽器發出讀取文件(URL 中域名後面部分對應的文件)的HTTP 請求,該請求報文做爲 TCP 三次握手的第三個報文的數據發送給服務器;
  • 4.服務器對瀏覽器請求做出響應,並把對應的 html 文本發送給瀏覽器;
  • 5.瀏覽器將該 html 文本並顯示內容;
  • 6.釋放 TCP鏈接(四次揮手);

上面這個問題是一個面試官很是喜歡問的問題,咱們下面把這6個步驟分解,逐步細談優化。html

1、DNS 解析

  • DNS`解析:將域名解析爲ip地址 ,由上往下匹配,只要命中便中止前端

    - 走緩存
    - 瀏覽器DNS緩存
    - 本機DNS緩存
    - 路由器DNS緩存
    - 網絡運營商服務器DNS緩存 (80%的DNS解析在這完成的)
    - 遞歸查詢
    複製代碼

優化策略:儘可能容許使用瀏覽器的緩存,能給咱們節省大量時間。java

2、TCP的三次握手

  • SYN (同步序列編號)ACK(確認字符)react

  • 第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等 待Server確認。webpack

  • 第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求創建鏈接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認鏈接請求,Server進入SYN_RCVD狀態。es6

  • 第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,若是正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,若是正確則鏈接創建成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間能夠開始傳輸數據了。web

3、瀏覽器發送請求

優化策略:面試

  • 1.HTTP協議通訊最耗費時間的是創建TCP鏈接的過程,那咱們就可使用HTTP Keep-Alive,在HTTP早期,每一個HTTP請求都要求打開一個TCP socket鏈接,而且使用一次以後就斷開這個TCP鏈接。 使用keep-alive能夠改善這種狀態,即在一次TCP鏈接中能夠持續發送多份數據而不會斷開鏈接。經過使用keep-alive機制,能夠減小TCP鏈接創建次數,也意味着能夠減小TIME_WAIT狀態鏈接,以此提升性能和提升http服務器的吞吐率(更少的tcp鏈接意味着更少的系統內核調用
  • 2.可是,keep-alive並非免費的午飯,長時間的TCP鏈接容易致使系統資源無效佔用。配置不當的keep-alive,有時比重複利用鏈接帶來的損失還更大。因此,正確地設置keep-alive timeout時間很是重要。(這個keep-alive_timout時間值意味着:一個http產生的tcp鏈接在傳送完最後一個響應後,還須要holdkeepalive_timeout秒後,纔開始關閉這個鏈接),若是想更詳細瞭解能夠看這篇文章keep-alve性能優化的測試結果
  • 3.使用webScoket通訊協議,僅一次TCP握手就一直保持鏈接,並且他對二進制數據的傳輸有更好的支持,能夠應用於即時通訊,海量高併發場景。webSocket的原理以及詳解
  • 4.減小HTTP請求次數,每次HTTP請求都會有請求頭,返回響應都會有響應頭,屢次請求不只浪費時間並且會讓網絡傳輸不少無效的資源,使用前端模塊化技術 AMD CMD commonJS ES6等模塊化方案將多個文件壓縮打包成一個,固然也不能都放在一個文件中,由於這樣傳輸起來可能會很慢,權衡取一箇中間值
  • 5.配置使用懶加載,對於一些用戶不馬上使用到的文件到特定的事件觸發再請求,也許用戶只是想看到你首頁上半屏的內容,可是你卻請求了整個頁面的全部圖片,若是用戶量很大,那麼這是一種極大的浪費
  • 6.服務器資源的部署儘可能使用同源策略
  • 7.在須要多個cookie去辨識用戶的多種情況時,使用session替代,把數據儲存在服務器端或者服務器端的數據庫中,這樣只須要一個cookie傳輸,節省大量的無效傳輸,並且儲存的數據能夠是永久無線大的。

4、服務器返回響應,瀏覽器接受到響應數據

5、瀏覽器解析數據,繪製渲染頁面的過程

  • 先預解析(將須要發送請求的標籤的請求發出去)
  • 從上到下解析html文件
  • 遇到HTML標籤,調用html解析器將其解析DOM
  • 遇到css標記,調用css解析器將其解析CSSOM
  • link 阻塞 - 爲了解決閃屏,全部解決閃屏的樣式
  • style 非阻塞,與閃屏的樣式不相關的
  • DOM樹和CSSOM樹結合在一塊兒,造成render
  • layout佈局 render渲染
  • 遇到script標籤,阻塞,調用js解析器解析js代碼,可能會修改DOM樹,也可能會修改CSSOM
  • DOM樹和CSSOM樹結合在一塊兒,造成render
  • layout佈局 render渲染(重排重繪)
  • script標籤的屬性
    • async 異步 誰先回來誰就先解析,不阻塞
    • defer 異步 按照前後順序(defer)解析,不阻塞
    • script標籤放在body下,放置屢次重排重繪,可以操做dom

性能優化策略:算法

  • 須要阻塞的樣式使用link引入,不須要的使用style標籤(具體是否須要阻塞看業務場景)
  • 圖片比較多的時候,必定要使用懶加載,圖片是最須要優化的,webpack4中也要配置圖片壓縮,能極大壓縮圖片大小,對於新版本瀏覽器可使用webp格式圖片webP詳解,圖片優化對性能提高最大。
  • webpack4配置 代碼分割,提取公共代碼成單獨模塊。方便緩存
/*
    runtimeChunk 設置爲 true, webpack 就會把 chunk 文件名所有存到一個單獨的 chunk 中,
    這樣更新一個文件只會影響到它所在的 chunk 和 runtimeChunk,避免了引用這個 chunk 的文件也發生改變。
    */
    runtimeChunk: true, 
    splitChunks: {
      chunks: 'all'  // 默認 entry 的 chunk 不會被拆分, 配置成 all, 就能夠了
    }
  }
    //由於是單入口文件配置,因此沒有考慮多入口的狀況,多入口是應該分別進行處理。
複製代碼
  • 對於須要事件驅動的webpack4配置懶加載的,能夠看這篇webpack4優化教程,寫得很是全面

  • 一些原生javaScriptDOM操做等優化會在下面總結

6、TCP的四次揮手,斷開鏈接


終結篇:性能只是 load 時間或者 DOMContentLoaded 時間的問題嗎?

  • RAIL
  • Responce 響應,研究代表,100ms內對用戶的輸入操做進行響應,一般會被人類認爲是當即響應。時間再長,操做與反應之間的鏈接就會中斷,人們就會以爲它的操做有延遲。例如:當用戶點擊一個按鈕,若是100ms內給出響應,那麼用戶就會以爲響應很及時,不會察覺到絲毫延遲感。
  • Animaton 現現在大多數設備的屏幕刷新頻率是60Hz,也就是每秒鐘屏幕刷新60次;所以網頁動畫的運行速度只要達到60FPS,咱們就會以爲動畫很流暢。
  • Idle RAIL規定,空閒週期內運行的任務不得超過50ms,固然不止RAIL規定,W3C性能工做組的Longtasks標準也規定了超過50毫秒的任務屬於長任務,那麼50ms這個數字是怎麼得來的呢?瀏覽器是單線程的,這意味着同一時間主線程只能處理一個任務,若是一個任務執行時間過長,瀏覽器則沒法執行其餘任務,用戶會感受到瀏覽器被卡死了,由於他的輸入得不到任何響應。爲了達到100ms內給出響應,將空閒週期執行的任務限制爲50ms意味着,即便用戶的輸入行爲發生在空閒任務剛開始執行,瀏覽器仍有剩餘的50ms時間用來響應用戶輸入,而不會產生用戶可察覺的延遲。
  • Load若是不能在1秒鐘內加載網頁並讓用戶看到內容,用戶的注意力就會分散。用戶會以爲他要作的事情被打斷,若是10秒鐘還打不開網頁,用戶會感到失望,會放棄他們想作的事,之後他們或許都不會再回來。

如何使網頁更絲滑?

  • 使用requestAnimationFrame

    • 即使你能保證每一幀的總耗時都小於16ms,也沒法保證必定不會出現丟幀的狀況,這取決於觸發JS執行的方式。假設使用 setTimeout 或 setInterval 來觸發JS執行並修改樣式從而致使視覺變化;那麼會有這樣一種狀況,由於setTimeout 或 setInterval沒有辦法保證回調函數何時執行,它可能在每一幀的中間執行,也可能在每一幀的最後執行。因此會致使即使咱們能保障每一幀的總耗時小於16ms,可是執行的時機若是在每一幀的中間或最後,最後的結果依然是沒有辦法每隔16ms讓屏幕產生一次變化,也就是說,即使咱們能保證每一幀整體時間小於16ms,但若是使用定時器觸發動畫,那麼因爲定時器的觸發時機不肯定,因此仍是會致使動畫丟幀。如今整個Web只有一個API能夠解決這個問題,那就是requestAnimationFrame,它能夠保證回調函數穩定的在每一幀最開始觸發。
  • 避免FSL

  • 先執行JS,而後在JS中修改了樣式從而致使樣式計算,而後樣式的改動觸發了佈局、繪製、合成。但JavaScript能夠強制瀏覽器將佈局提早執行,這就叫 強制同步佈局FSL

    //讀取offsetWidth的值會致使重繪
     const newWidth = container.offsetWidth;
       
      //設置width的值會致使重排,可是for循環內部
      代碼執行速度極快,當上面的查詢操做致使的重繪
      尚未完成,下面的代碼又會致使重排,並且這個重
      排會強制結束上面的重繪,直接重排,這樣對性能影響
      很是大。因此咱們通常會在循環外部定義一個變量,這裏
      面使用變量代替container.offsetWidth;
     boxes[i].style.width = newWidth + 'px';
    }
    複製代碼
  • 使用transform屬性去操做動畫,這個屬性是由合成器單獨處理的,因此使用這個屬性能夠避免佈局與繪製。

  • 使用translateZ(0)開啓圖層,減小重繪重排。特別在移動端,儘可能使用transform代替absolute。建立圖層的最佳方式是使用will-change,但某些不支持這個屬性的瀏覽器可使用3D 變形(transform: translateZ(0))來強制建立一個新層。

  • 有興趣的能夠看看這篇文字 前端頁面優化

  • 樣式的切換最好提早定義好class,經過class的切換批量修改樣式,避免屢次重繪重排

  • 能夠先切換display:none再修改樣式

  • 屢次的append操做能夠先插入到一個新生成的元素中,再一次性插入到頁面中。

  • 代碼複用,函數柯里化,封裝高階函數,將屢次複用代碼封裝成普通函數(俗稱方法),React中封裝成高階組件,ES6中可使用繼承,TypeScript中接口繼承,類繼承,接口合併,類合併。

  • 在把數據儲存在localstorage和sessionstorage中時,能夠再本身定義一個模塊,把這些數據在內存中存儲一份,這樣只要能夠直接從內存中讀書,速度更快,性能更好。

  • 能不定義全局變量就不定義全局變量,最好使用局部變量代替全局變量,查找的速度要高一倍。

  • 強力推薦閱讀:阮一峯ES6教程

  • 以及什麼是TypeScript以及入門


以上都是根據本人的知識點總結得出,後期還會有更多性能優化方案等出來,路過點個贊收藏收藏~,歡迎提出問題補充~


下面加入React的性能優化方案:

  • 在生命週期函數shouldComponentUpdate中對this.stateprev state進行淺比較,使用for-in循環遍歷二者, 只要獲得他們每一項值,只要有一個不同就返回true,更新組件。

  • 定義組件時不適用React.component , 使用PureComponent代替,這樣React機制會自動在shouldComponentUpdate中進行淺比較,決定是否更新。

  • 上面兩條優化方案只進行淺比較,只對比直接屬性的值,固然你還能夠在上面加入this.propsprevprops的遍歷比較,由於shouldComponentUpdate的生命週期函數自帶這兩個參數。若是props 和 state的值比較複雜,那麼可使用下面這種方式去進行深比較。

  • 解決:

    • 保證每次都是新的值
    • 使用 immutable-js 庫,這個庫保證生成的值都是惟一的
      var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
      var map2 = map1.set('b', 50);
      map1.get('b'); // 2
      map2.get('b'); // 50
      複製代碼
  • 總結:使用以上方式,能夠減小沒必要要的重複渲染。

  • ReactJSX語法要求必須包裹一層根標籤,爲了減小沒必要要的DOM層級,咱們使用Fragment標籤代替,這樣渲染時候不會渲染多餘的DOM節點,讓DIFF算法更快遍歷。

  • 使用Redux管理全局多個組件複用的狀態。

  • React構建的是SPA應用,對SEO不夠友好,能夠選擇部分SSR技術進行SEO優化。

  • Ant-design這類的UI組件庫,進行按需加載配置,從import Button from 'antd' 的引入方式,變成import {Button} from antd的方式引入。(相似Babel7中的runtime和polifill的區別).

  • React中一些數據的須要更新,可是卻不急着使用,或者說每次更新的這個數據不須要更新組件從新渲染的,能夠按期成類的實例上的屬性,這樣能減小屢次的重複無心義的DIFF和渲染。

  • Redux的使用要看狀況使用,若是隻是一個局部狀態(僅僅是一個組件或者父子組件使用就不要使用Redux)。對於一個父子、父子孫多層組件須要用到的state數據,也可使用context上下文去傳遞. Context上下文詳解,可是複雜項目的多個不一樣層次組件使用到的state,必須上Redux

  • 全部的原生監聽事件,定時器等,必須在componentWillUnmount中清除,不然大型項目一定會發生內存泄露,極度影響性能!!!