前言,本文翻譯自docs.google.com/presentatio…看到之後感覺講解的系統清晰明瞭,實屬一篇好文。就加上自己的理解翻譯了一下,聊以加深印象。
硬體、網路,對效能的而言始終不能避開的兩個物理因素
一、 硬體如何影響效能
硬體(即處理能力)決定了計算密集型任務的表現
瀏覽器必須解析、編譯並執行所有的js,如下如所示:
對於每個階段而言,程式碼量的差異顯然會影響其變現即影響效能,這種差異在低處理能力的機器上的體現尤為明顯。
當然其他型別的資源請求也會影響效能,相比之下js的影響是比較突出的。
所以考慮不同使用者cpu的狀況,減少js怪物(即縮小js體積)是很必要的。可以從以下幾方面著手:
- 刪除不必要js
- 延遲載入非關鍵js
- 藉助相關工具
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+響應在資料相應之前。
- DNS 查詢(可能)
- TLS 握手(可能)
- 請求資源
初始狀態連線不能被充分利用
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 優化關鍵路徑
優化頁面渲染或者載入時所需的事件以便儘可能的加快完成。
瀏覽器優化資源請求
對於所有的請求,瀏覽器對其是有權重處理的,即分不同的優先順序來載入。具體來說就是重要會阻塞渲染的優先順序比較高。
如下圖所示:
使用資源提示
通過以下方式,提前載入或者請求將要用到的內容:
- Dns-prefresh
- preconnect
- Preload(當前頁面)
- Prefetch(下個頁面)
2.3 降低請求大小
- 使用Brotli壓縮
相對於gzip
更好的壓縮比,檔案越大越明顯
更快的解壓縮
壓縮速度極大提升 - 減少js體積
- 優化圖片
23就不再多提了,方式有很多。
結束語
對於好的資源,多讀收益還是很明顯的。這次翻譯感覺體會又多了一些,不過由於本人才疏學淺,如有錯誤還望多多指正。一言概之,共同學習。