我們把性能優化的方向分為以下兩個方面,有助于結構化的思考與系統分析。
加載性能。如何更快地把資源從伺服器中拉到浏覽器,如 http 與資源體積的各種優化,都是旨在加載性能的提升。
渲染性能。如何更快的把資源在浏覽器上進行渲染。如減少重排重繪,rIC 等都是旨在渲染性能的提升。

LCP: 加載性能。最大内容繪制應在 2.5s 内完成。
FID: 互動性能。首次輸入延遲應在 100ms 内完成。
CLS: 頁面穩定性。累積布局偏移,需手動計算,CLS 應保持在 0.1 以下。
web-vitals
當收集浏覽器端每個使用者核心性能名額時,可通過 <code>web-vitals</code> 收集并通過 sendBeacon 上報到打點系統。
将資源分發到 CDN 的邊緣網絡節點,使使用者可就近擷取所需内容,大幅減小了光纖傳輸距離,使全球各地使用者打開網站都擁有良好的網絡體驗。
<code>http2</code> 的諸多特性決定了它更快的傳輸速度。
多路複用,在浏覽器可并行發送 N 條請求。
首部壓縮,更小的負載體積。
請求優先級,更快的關鍵請求
目前,網站已大多上了 http2,可在控制台面闆進行檢視。
由于 http2 可并行請求,解決了 http1.1 線頭阻塞的問題,以下幾個性能優化點将會過時
資源合并。如 <code>https://shanyue.tech/assets??index.js,interview.js,report.js</code>
域名分片。
雪碧圖。将無數小圖檔合并成單個大圖檔。
更好的資源緩存政策,對于 CDN 來講可減少回源次數,對于浏覽器而言可減少請求發送次數。無論哪一點,對于二次網站通路都具有更好的通路體驗。
緩存政策
強緩存: 打包後帶有 hash 值的資源 (如 /build/a3b4c8a8.js)
協商緩存: 打包後不帶有 hash 值的資源 (如 /index.html)
分包加載 (Bundle Spliting)
避免一行代碼修改導緻整個 bundle 的緩存失效
對一個網站的資源進行壓縮優化,進而達到減少 HTTP 負載的目的。
js/css/image 等正常資源體積優化,這是一個大話題,再以下分别讨論
小圖檔優化,将小圖檔内聯為 Data URI,減小請求數量
圖檔懶加載
新的 API: IntersectionObserver API
新的屬性: loading=lazy
對 JS、CSS、HTML 等文本資源均有效,但是對圖檔效果不大。
<code>gzip</code> 通過 LZ77 算法與 Huffman 編碼來壓縮檔案,重複度越高的檔案可壓縮的空間就越大。
<code>brotli</code> 通過變種的 LZ77 算法、Huffman 編碼及二階文本模組化來壓縮檔案,更先進的壓縮算法,比 gzip 有更高的性能及壓縮率
可在浏覽器的 <code>Content-Encoding</code> 響應頭檢視該網站是否開啟了壓縮算法,目前知乎、掘金等已全面開啟了 <code>brotli</code> 壓縮。
Terser 是 Javascript 資源壓縮混淆的神器。
它可以根據以下政策進行壓縮處理:
長變量名替換短變量
删除空格換行符
預計算: <code>const a = 24 * 60 * 60 * 1000</code> -> <code>const a = 86400000</code>
移除無法被執行的代碼
移除無用的變量及函數
可在 Terser Repl 線上檢視代碼壓縮效果。
swc 是另外一個用以壓縮 Javascript 的工具,它擁有與 <code>terser</code> 相同的 API,由于它是由 <code>rust</code> 所寫,是以它擁有更高的性能。
html-minifier-terser 用以壓縮 HTML 的工具
關于更小的 Javascript,上邊已總結了兩條:
gzip/brotli
terser (minify)
還有以下幾點可以考慮考慮:
路由懶加載,無需加載整個應用的資源
Tree Shaking: 無用導出将在生産環境進行删除
browserlist/babel: 及時更新 browserlist,将會産生更小的墊片體積
再補充一個問題:
如何分析并優化目前項目的 Javascript 體積?如果使用 <code>webpack</code> 那就簡單很多。
使用 <code>webpack-bundle-analyze</code> 分析打包體積
對一些庫替換為更小體積的庫,如 moment -> dayjs
對一些庫進行按需加載,如 <code>import lodash</code> -> <code>import lodash/get</code>
對一些庫使用支援 Tree Shaking,如 <code>import lodash</code> -> <code>import lodash-es</code>
在前端發展的現在,<code>webp</code> 普遍比 <code>jpeg/png</code> 更小,而 <code>avif</code> 又比 <code>webp</code> 小一個級别
為了無縫相容,可選擇 <code>picture/source</code> 進行回退處理
更合适的尺寸: 當頁面僅需顯示 100px/100px 大小圖檔時,對圖檔進行壓縮到 100px/100px
更合适的壓縮: 可對前端圖檔進行适當壓縮,如通過 <code>sharp</code> 等
以下五個步驟為關鍵渲染路徑
HTML -> DOM,将 html 解析為 DOM
CSS -> CSSOM,将 CSS 解析為 CSSOM
DOM/CSSOM -> Render Tree,将 DOM 與 CSSOM 合并成渲染樹
RenderTree -> Layout,确定渲染樹中每個節點的位置資訊
Layout -> Paint,将每個節點渲染在浏覽器中
渲染的優化很大程度上是對關鍵渲染路徑進行優化。
<code>preload</code>/<code>prefetch</code> 可控制 HTTP 優先級,進而達到關鍵請求更快響應的目的。
preload 加載目前路由必需資源,優先級高。一般對于 Bundle Spliting 資源與 Code Spliting 資源做 preload
prefetch 優先級低,在浏覽器 idle 狀态時加載資源。一般用以加載其它路由資源,如當頁面出現 Link,可 prefetch 目前 Link 的路由資源。(next.js 預設會對 link 做懶加載+prefetch,即當某條 Link 出現頁面中,即自動 prefetch 該 Link 指向的路由資源
捎帶說一下 <code>dns-prefetch</code>,可對主機位址的 DNS 進行預解析。
防抖:防止抖動,機關時間内事件觸發會被重置,避免事件被誤傷觸發多次。代碼實作重在清零 clearTimeout。防抖可以比作等電梯,隻要有一個人進來,就需要再等一會兒。業務場景有避免登入按鈕多次點選的重複送出。
節流:控制流量,機關時間内事件隻能觸發一次,與伺服器端的限流 (Rate Limit) 類似。代碼實作重在開鎖關鎖 timer=timeout; timer=null。節流可以比作過紅綠燈,每等一個紅燈時間就可以過一批。
無論是防抖還是節流都可以大幅度減少渲染次數,在 React 中還可以使用 <code>use-debounce</code> 之類的 hooks 避免重新渲染。
這又是一個老生常談的話題,一般在視口内維護一個虛拟清單(僅渲染十幾條條資料左右),監聽視口位置變化,進而對視口内的虛拟清單進行控制。
在 React 中可采用以下庫:
react-virtualized
react-window
在一些前端系統中,當加載頁面時會發送請求,路由切換出去再切換回來時又會重新發送請求,每次請求完成後會對頁面重新渲染。
然而這些重新請求再大多數時是沒有必要的,合理地對 API 進行緩存将達到優化渲染的目的。
對每一條 GET API 添加 key
根據 key 控制該 API 緩存,重複發生請求時将從緩存中取得
試舉一例:
在純浏覽器中,如何實作高性能的實時代碼編譯及轉換?
Babel Repl
如果純碎使用傳統的 Javascript 實作,将會耗時過多阻塞主線程,有可能導緻頁面卡頓。
如果使用 <code>Web Worker</code> 交由額外的線程來做這件事,将會高效很多,基本上所有在浏覽器端進行代碼編譯的功能都由 <code>Web Worker</code> 實作。
JS 性能低下
C++/Rust 高性能
使用 C++/Rust 編寫代碼,然後在 Javascript 環境運作
在純浏覽器中,如何實作高性能的圖檔壓縮?
基本上很難做到,Javascript 的性能與生态決定了實作圖檔壓縮的艱難。
而借助于 WASM 就相當于借用了其它語言的生态。
libavif: C語言寫的 avif 解碼編碼庫
libwebp: C語言寫的 webp 解碼編碼庫
mozjpeg: C語言寫的 jpeg 解碼編碼庫
oxipng: Rust語言寫的 png 優化庫
而由于 WASM,完全可以把這些其它語言的生态移植到浏覽器中,進而實作一個高性能的離線式的圖檔壓縮工具。
如果想了解這種的工具,請看看 squoosh
文章分類