提升現代web app中的頁面效能

瀟湘待雨發表於2019-02-15

前言,本文翻譯自docs.google.com/presentatio…看到之後感覺講解的系統清晰明瞭,實屬一篇好文。就加上自己的理解翻譯了一下,聊以加深印象。

硬體、網路,對效能的而言始終不能避開的兩個物理因素

一、 硬體如何影響效能

硬體(即處理能力)決定了計算密集型任務的表現
瀏覽器必須解析、編譯並執行所有的js,如下如所示:
提升現代web app中的頁面效能
對於每個階段而言,程式碼量的差異顯然會影響其變現即影響效能,這種差異在低處理能力的機器上的體現尤為明顯。
當然其他型別的資源請求也會影響效能,相比之下js的影響是比較突出的。
所以考慮不同使用者cpu的狀況,減少js怪物(即縮小js體積)是很必要的。可以從以下幾方面著手:

  1. 刪除不必要js
  2. 延遲載入非關鍵js
  3. 藉助相關工具

1.1 刪除不必要js

只在必要的時候進行轉換

僅僅對需要ES5的客戶端才進行轉換,80%的瀏覽器已經支援ES2015。(結合自己實際開發情況,移動端而言確實80%+的手機已經支援ES2015,僅僅只遇到oppop,vivio這兩中手機不支援。)因為轉換之後的代價還是有的,如下所示:

//ES2015
books.map(b => b.title);
//ES5
books.map(function(b) { return b.title; }, this);
//體積大了一倍
複製程式碼

使用壓縮工具/優化工具

像UglifyJS & Closure Compiler 之類的工具,在壓縮之外還有一些優化功能。
對大多數的js而言壓縮程式碼中空格移除和符號修改佔了95%的工作量,並非是精心的程式碼轉換。
壓縮不應該是盲目的,應該平衡下面幾點。

  • 更好的壓縮比
  • 高額的計算機資源消耗
  • 前期準備
  • 可能的副作用
    壓縮可能不是一味的追求體積更小,相對而言,壓縮也應該權衡一下其他方面。比較常見就是程式碼壓縮時相比於其他流程,超長的時間消耗。壓縮之後可能遇到關鍵字的問題。
    如何解決其實應該是從本身專案出發。
  • 儘可能的優化可快取的靜態資源
  • 在壓縮體積和時間之間找到一個平衡點

使用tree-shaking移除沒用的程式碼

和壓縮程式碼的目的一致,減小資源大小,不過是從另一個層面的解決方案。像webpack,rollup都提供了該功能。
tree-shaking會將沒有被用到的exports移除

//tool 
//used
export function a(){
    console.log(`1`)
}
export function b(){
    console.log(`2`)
}
//app.js
import {a} from `./tool`
a()
複製程式碼

function b 未被使用,最終的打包檔案中b將會被刪除。

ES2015的模組是靜態的,可以使用tree-shaking

import/export 在執行之前就被確定,並且兩者只能在頂層,沒有條件邏輯的情況下使用(畢竟未執行)

tree-shaking的侷限

  • 僅僅刪除未被使用的匯出
  • 不支援所有的程式碼庫(僅僅ES2015)
  • 可能做不到極致
    難以確定刪除是否會有副作用,這種打包器只能保留

自我排查

工具不能做到盡善盡美,並且在執行之前確定某項問題是困難的。
當前來說應該從程式碼規範和程式碼註釋來自我完善。

對於框架

如果非必須,請不要使用。大的框架至少300kb的體積。
當然必要,請基於下面幾點來選擇:

  • 服務端渲染
  • 懶載入
  • 程式碼優化
  • 效能

1.2 延遲載入非必需js

先看一下js不同引入方式的差別

預設方式 Async Defer
阻塞渲染
執行時機 載入完成 載入完成 document解析完成

使用程式碼分割和懶載入

  • 減少啟動時需要載入的js
  • 儘可能少的載入不相關的js
    傳統的做法是載入Bundle js,程式碼分割是將程式碼分成不同的chunk
    這裡同樣有兩種極端:
  • 每個模組對應一個js
    不好壓縮
    利於快取
    粒度更小
  • 整個應用只對應一個js
    便於壓縮
    不利於快取
    粒度太大,即可維護性
    忽然有種中庸的感覺了,凡事皆有度,所有單一操作都不能過分苛求極致,兼顧才是合理

1.3 使用其他工具

使用html和css

某些狀況下可能需要vanilla JS(即原生js),框架帶來便利的同時不可避免的有其他的一些效能消耗。提到這裡有一篇文章大家可以看一下我是怎麼把我的 React 應用換成 VanillaJS 的(這是不是一個壞主意)
舉個例子:
Netflix 降低了他們登入頁50%的TTI(傳輸時間間隔)通過下面的方式:

  • 使用原生js來代替React
  • 當使用者登入的時候載入餘下的部分

使用server

將代價昂貴的庫放到server端,使用ssr來代替client-side-render.
ssr可以將我們初始頁面載入事件減少到原來的1/5並減少不同瀏覽器之間的差異。
ssr確實首屏的優化確實很大,優點不多說。但這裡提一句,不要盲目ssr,特別是初次請求響應時間較長的介面

二、網路的影響

首先了解兩個概念:

  • 頻寬: 資料吞吐量(位元/秒)
  • 延遲: 延遲資料傳輸時間(ms)

對於大部分市場來說,頻寬是可以滿足需求的(這裡統計是國外的,平均26兆,國內略低一點),平均頁面大小3.5Mb。傳輸時間(3.5/26)0.13s。國內會差一點。
延遲對效能影響比較明顯。
行動網路的延遲

網路 延遲ms
5G <=4
4G <=100
3G 100-500
3G 300-1000

適應行動網路的限制

應該從下面幾方面來分別考慮。

  • 減少請求數量
  • 優化關鍵路徑
  • 減少請求大小

2.1 減少請求數量

新建一次連線的代價是昂貴的,要重複以下過程

建立連線需要1至3+響應在資料相應之前。

  1. DNS 查詢(可能)
  2. TLS 握手(可能)
  3. 請求資源

初始狀態連線不能被充分利用

TCP slow-start限制了在初始響應裡裡資料被髮送的數量

傳送更多的資料通常情況下比新建連線要划算。

請求的體積與相應時間並不是線性關係。
兩次50k的請求消耗比一次100k的大了不少。

減少重定向的使用

  • 重定向增加了伺服器昂貴的迴圈
  • server-side 相對於client-side來說重定向優秀一點(快並且可快取)
  • 看一下301和302的響應code

使用快取

理想狀態下,確實資源是否最新不應該通過網路請求
可以通過下面的方式:

  • 使用Content-addressed URLs:
    即內容與地址對應,log13234d.jpg而非log.jpg
  • 使用max-age

這種瀏覽器調整為Facebook節省了60%的請求

使用service workers來增強快取

service worker可以幫組我們:

  • 攔截網路請求
  • 訪問瀏覽器快取
  • 代替傳送網路請求來處理過期的資源

使用http2

使用HTTP2時,每個來源只需要一個連線,減少了連線建立的開銷。

2.2 優化關鍵路徑

優化頁面渲染或者載入時所需的事件以便儘可能的加快完成。

瀏覽器優化資源請求

對於所有的請求,瀏覽器對其是有權重處理的,即分不同的優先順序來載入。具體來說就是重要會阻塞渲染的優先順序比較高。
如下圖所示:
提升現代web app中的頁面效能

使用資源提示

通過以下方式,提前載入或者請求將要用到的內容:

  • Dns-prefresh
  • preconnect
  • Preload(當前頁面)
  • Prefetch(下個頁面)

2.3 降低請求大小

  • 使用Brotli壓縮
    相對於gzip
    更好的壓縮比,檔案越大越明顯
    更快的解壓縮
    壓縮速度極大提升
  • 減少js體積
  • 優化圖片
    23就不再多提了,方式有很多。

結束語

對於好的資源,多讀收益還是很明顯的。這次翻譯感覺體會又多了一些,不過由於本人才疏學淺,如有錯誤還望多多指正。一言概之,共同學習。

相關文章