Web前端效能優化深度解讀,這些細節千萬不能忽視

語言: CN / TW / HK

導讀: 使用者體驗是web產品非常重要的部分,核心是讓使用者使用舒服,幫助使用者流暢地得到所求,使用者體驗的優劣甚至會影響到使用者的留存。體驗差的網站各有各的不同,但是體驗好的網站往往都有一些共性,這些優秀的特徵凝結了設計師、研發工程師和產品經理的大量智慧。

  • 訪問互動速度迅速

  • 動畫效果順滑流暢

  • 有使用者操作的反饋

  • 簡單的操作步驟

  • 整站體驗一致性

  • 主體內容在最顯眼的位置

  • 無障礙訪問,不同的人群均可使用

在這些優秀體驗的特性中,最容易讓人產生共鳴的往往是網站的效能問題,比如網站的訪問互動速度。如何發現效能問題?效能如何優化(效能優化的常規方法和框架方法)?如何衡量收益?本文根據多年在效能優化方面的實踐,著重分享一下首屏效能優化的一些經驗。

01

效能採集

工欲利器事,必先利其器。我們所說的效能採集並不是效能分析Devtools,而是指在產品真實使用者訪問的大資料中進行抽樣,對於抽樣使用者進行效能資料採集,得到真實使用者環境下產品效能資料。各瀏覽器廠商都已認識到效能對於web開發的重要性,為了解決當前效能測試的困難,W3C推出了一套效能API標準,目的是簡化開發者對網站效能進行精確分析與控制的過程,方便開發者採取手段提高web效能。整套標準包含了10餘種API,在下圖中可以看到它們當前在規範流程中的進展。

圖:效能API標準(摘錄51CTO圖片)

這套標準中提供了導航定時(Navigation Timing)、資源定時(Resource Timing)、使用者定時User Timing和效能時間線(Performance Timeline)規範可以幫助開發人員精確地測量文件的導航時間,在頁面上獲取資源的情況,以及開發人員指令碼執行情況。

在這套API中,頁面載入Navigation Timing和頁面資源載入Resource Timing這兩個API可以幫助我們獲取頁面的Domready時間、onload時間、白屏時間以及單個頁面資源在從傳送請求到獲取到response各階段例如頻寬、延遲或主頁的整體頁面載入時間的效能引數,這些都是基於真實使用者資料(RUM)。

圖:Navigation Timing關係圖(摘錄W3C)

在獲取使用者訪問Timing資料的前提下,我們可以結合具體業務場景定義訪問效能的核心指標,例如白屏時間、首屏時間FSP、使用者可互動時間TTI、頁面onload時間等作為核心優化指標,其中首屏時間和使用者可互動時間需要單獨埋點自定義。

還可以通過獲取DNS查詢耗時、TCP連結耗時、request請求耗時、解析dom樹耗時、白屏時間、domready時間、onload時間等做效能分析,後續根據症狀對這些細緻階段做效能優化,這些引數是通過上面的performance.timing各個屬性的差值組成的。

通過使用API對各個階段效能指標進行採集,等待到所有資料都獲取完成之後,通過網路請求將資料傳送到伺服器用作後續資料分析使用。

02

效能優化

快速載入、及時響應使用者反饋、提供流暢的動畫、以及擁有類似原生APP一般沉浸的使用者體驗是web應用在效能優化上的目標,這主要關係到載入效能和渲染效能兩個方面,本章節介紹一些常規優化方法和框架級優化方案。

2.1 載入效能優化

Web 頁面通常由 HTML、CSS、JavaScript 和其他多媒體資源組成,充斥著各種同步資源和非同步資源。頁面載入時,必須從伺服器獲取這些資源。

2.1.1 減小資源體積

  • 壓縮文字內容

  • 優化JavaScript第三方庫引入

壓縮雖然簡單,但十分有效,這也是最廣泛的優化資源體積的操作。許多工具可以幫助我們完成HTML、CSS、JavaScript、圖片等壓縮。例如,TerserPlugin可以用於壓縮 JavaScript,PostCSS可以對 CSS 進行壓縮,以及完成字首自動補全工作。除了壓縮單個檔案外,在伺服器上配置 Gzip 也十分重要。Gzip 對文字資源的壓縮效果非常明顯,通常可以將體積再壓縮至原本的 30% 左右,但 Gzip 對已經單獨壓縮的影象等非文字資源來說,效果並不好。

如果我們只需要使用工具庫中少數幾個簡單函式,可以考慮使用原生 JavaScript 代替。不計後果地引入第三方庫,會迅速增大 JavaScript 資源的體積。

2.1.2 對資源進行快取

快取在優化頁面載入效能的工作中有舉足輕重的作用,快取無處不在,包括瀏覽器端、網路代理、服務端快取,往往能大幅加快響應速度。

圖:web全鏈路快取

  • HTTP 快取

  • Local Storage

  • Cache Storage

  • IndexedDB

  • CDN

現代瀏覽器都實現了 HTTP 快取機制。瀏覽器在初次獲取資源後,會根據 HTTP 響應頭部的Cache-Control和ETag欄位,來決定該資源的強快取策略或者協商快取策略。

Local Storage主要是用來作為本地儲存來使用的,解決了cookie儲存空間不足的問題(cookie中每條cookie的儲存空間為4k),localStorage中一般瀏覽器支援的是5M大小。

Cache Storage它用來儲存 Response 物件的,也就是說用來對 HTTP響應做快取的,通常在PWA技術中使用。

IndexedDB是一種在瀏覽器中持久儲存資料的方法,允許我們不考慮網路可用性,建立具有豐富查詢能力的可離線web應用程式。

內容快取在CDN網路節點,位於使用者接入點,是面向終端使用者的內容提供裝置,可快取靜態Web內容和流媒體內容,實現內容的邊緣傳播和儲存,以便使用者的就近訪問。

2.1.3 調整資源優先順序

通過調整資源載入優先順序,保證主體內容能夠較快的被載入完成,通過預載入、懶載入等多種方式,調整資源載入的行為,優化網頁載入效能。

  • 預載入

  • 預連線與 DNS 預解析

  • 預取

  • 懶載入

  • Service Worker

通過來提前聲明當前頁面所需的資源,以便瀏覽器能預載入這些資源。通過media屬性進行媒體查詢,根據響應式的情況選擇性地預載入資源。

預連線會提前完成 DNS 解析、TCP 握手和 TLS 協商的工作,但並不會提前載入資源。也可以考慮使用,提前與資源建立 socket 連線。

瀏覽器會在空閒時,使用最低優先順序下載預取的資源。預取通過宣告,通常用於點選“下一頁”的頁面動作之前提前載入使用者接下來可能需要的html資源。

按需載入和延時載入都屬於懶載入的範疇,例如對影象資源採用“懶載入”策略,即僅載入當前在視口內的影象,對於視口外未載入的影象,在其即將滾動進入視口時才開始載入。

利用Service Worker 執行緒脫離在主執行緒之外來進行 Web 資源和請求的持久離線快取。

2.1.4 合理拆分程式碼

瀏覽器支援並行載入資源,合理拆分資源也是一種有效的優化方法。為了更好的效果,我們往往不需要在首屏一次性載入所有 JavaScript 程式碼,合理的拆分程式碼、區分開發和生產環境使用少量主要程式碼,將當前暫時不需要的程式碼拆分出去可以有效加快首屏展現的速度。通過webpack區分開發環境和生產環境差異化配置打包資源可以有效優化程式碼,Tree shaking使得模組間依賴可以通過靜態分析來更好地優化剪枝(僅ES modules支援)。webpack-bundle-analyzer 是一個關於 webpack 構建產物的視覺化外掛,可以清晰地看到構建產物的體積,幫助分析後續的優化方向。

2.1.5 HTTP/2

HTTP/2帶給WEB帶來了很大的效能提升,同時多路複用、頭部壓縮、Server Push等特點,使得可以在一個連線上同時開啟多個流雙向傳輸資料,服務端可以在傳送頁面 HTML 時主動推送其它資源,而不用等到瀏覽器解析到相應位置,發起請求再響應。

圖:http1 vs http2

2.2 渲染效能優化

瀏覽器在渲染頁面前,首先會將 HTML 文字內容解析為 DOM,將 CSS 解析為 CSSOM。DOM 和 CSSOM 都是樹狀資料結構,兩者相互獨立,但又有相似之處。接著,瀏覽器會將 DOM 和 CSSOM 樹合併成渲染樹。從 DOM 樹的根節點開始遍歷,並在 CSSOM 樹中查詢節點對應的樣式規則,合併成渲染樹中的節點。在遍歷的過程中,不可見的節點將會被忽略。渲染樹隨後會被用於佈局,就是計算渲染樹節點在瀏覽器視口中確切的位置和大小。瀏覽器進行一次佈局的效能開銷較大,我們需要小心地避免頻繁觸發頁面重新佈局。得到渲染樹節點的幾何佈局資訊後,瀏覽器就可以將節點繪製到螢幕上了,包括繪製文字、顏色、邊框和陰影等。

繪製的過程,首先會根據佈局和視覺相關的樣式資訊生成一系列繪製操作,隨後執行柵格化(柵格化是將向量圖形格式表示的影象轉換成點陣圖以用於顯示器或者印表機輸出的過程),將待繪製項轉換為點陣圖儲存在 GPU 中,最終通過圖形庫將畫素繪製在螢幕上。

圖:瀏覽器渲染過程

頁面不是一次性被繪製出來的。實際上,頁面被分成了多個圖層進行繪製,這些圖層會在另一個單獨的執行緒裡繪製到螢幕上,這個過程被稱作合成。合成執行緒可以對圖層進行剪下、變換等處理,因此可以用於響應使用者基本的滾動、縮放等操作,又不會受到主執行緒阻塞的影響。

2.2.1 關鍵渲染路徑

由於渲染都是在主程序中執行的,所以合理的利用主程序渲染非常重要。首屏渲染所必須的關鍵資源,共同組成了關鍵渲染路徑,減少非關鍵渲染路徑的資源消耗可以有效提升渲染速度。

  • 延遲非關鍵 CSS 載入

  • async 和 defer

Web 應用中往往會有一些首屏渲染時用不到的 CSS,如彈框的樣式等。通過引用的 CSS 都會在載入時阻塞頁面渲染。為了使這些非關鍵 CSS 不阻塞頁面渲染,可以通過拆分資源的方式並延遲非關鍵資源載入。

由於渲染都是在主程序中執行的,所以合理的利用主程序渲染非常重要。首屏渲染所必須的關鍵資源,共同組成了關鍵渲染路徑,減少非關鍵渲染路徑的資源消耗可以有效提升渲染速度。

2.2.2 非阻塞 JavaScript

使用者對於不流暢的滾動或動畫十分敏感,一般要求頁面幀率應達到每秒 60 幀。由於 JavaScript 一般是單執行緒執行的,長時間執行的任務會阻塞瀏覽器的主執行緒,使頁面失去響應,出現卡頓和假死的現象。

  • 頁面滾動

  • requestAnimationFrame 任務在瀏覽器渲染下一幀之前執行

  • requestIdleCallback 將任務安排在瀏覽器空閒時執行

  • Web Workers

當我們監聽 touchstart、touchmove 等事件時,由於合成執行緒並不知道我們是否會通過 event.preventDefault() 來阻止預設的滾動行為,從而在每次事件觸發時,都會等待事件處理函式執行完畢後再進行頁面滾動。這通常會導致較明顯的延遲,影響頁面滾動的流暢性。通過在addEventListener()時宣告{passive: true},來表明事件處理函式不會阻止頁面滾動,使得使用者的操作更快得到響應。

我們可以將一些耗效能的邏輯放在 worker 執行緒中進行處理,這樣主執行緒就能繼續響應使用者操作和渲染頁面了。

2.2.3 降低渲染樹計算複雜性

結構越複雜的頁面往往效能越差,動畫多的頁面出現卡頓的機率也越大。

  • 減少查詢與元素匹配成本

  • 減少佈局次數

  • 優化繪製與合成

渲染樹由 DOM 和 CSSOM 樹合併而成,對於每個 DOM 元素,需要查詢與元素匹配的樣式規則。CSS Modules 是一種較為主流的 CSS-in-JS 解決方案,利用 webpack 等構建工具,可以對類選擇器生成自定義格式的唯一類名,同樣能減少瀏覽器匹配 CSS 選擇器的開銷。

瀏覽器進行一次佈局的開銷很大,所以我們需要儘可能避免直接修改這些屬性,尤其是不應將佈局屬性用於動畫效果,否則會出現明顯的掉幀現象。

修改絕大多數樣式屬性都會導致頁面重繪,這很難避免。僅有的例外是transform和opacity,這是由於它們可以僅由合成器操作圖層來實現。transform和opacity非常適合用於實現動畫效果,但我們仍需要通過will-change為它們建立獨立的圖層,避免影響其他圖層的繪製。

2.3 框架優化方法

CSR、SSR、NSR、ESR、hybrid離線包、Big pipe、app cache等,都是不錯的方法。

2.3.1 CSR(Client Side Render)

瀏覽器渲染顧名思義就是所有的頁面渲染、邏輯處理、頁面路由、介面請求均是在瀏覽器中發生,也就是從服務端請求一個簡單HTML檔案然後通過執行JavaScript在HTML上進行內容的新增。其實,現代主流的前端框架均是這種渲染方式,這種渲染方式的好處在於實現了前後端架構分離,利於前後端職責分離,並且能夠首次渲染迅速有效減少白屏時間。同時,CSR可以通過在打包編譯階段進行預渲染或者骨架屏生成,可以進一步提升首次渲染的使用者體驗。

圖:CSR

2.3.2 SSR(Server Side Render)

服務端渲染則是在服務端完成頁面的渲染,在服務端完成頁面模板、資料填充、頁面渲染,然後將完整的HTML內容返回給到瀏覽器。由於所有的渲染工作都在服務端完成,因此網站的首屏時間和TTI都會表現比較好。

圖:SSR

但是,渲染需要在服務端完成,並不能很好進行前後端職責分離,而且白屏時間也會比較長,同時,對於服務端的負載要求也會比較高。

2.3.3 NSR(Native Side Render)

GMTC2019 全球大前端技術上 UC 團隊提到了 0.3 秒的 “閃開” 方案。這種方案適用於混合開發,NSR本質是分散式SSR,通過載入離線頁面模板,Ajax預載入頁面資料,Native渲染生成Html資料並且快取在客戶端,將伺服器的渲染工作放在了一個個獨立的移動裝置中,實現了頁面的預載入,同時又不會增加額外的伺服器壓力。核心思路是藉助瀏覽器啟用一個 JS-Runtime,提前將下載好的 html 模板及預取的 feed 流資料進行渲染,然後將 html 設定到記憶體級別的 MemoryCache 中,從而達到點開即看的效果。

圖:NSR

2.3.4 ESR(Edge Side Render)

邊緣渲染的核心思想是,藉助邊緣計算的能力,將靜態內容與動態內容以流式的方式,先後返回給使用者。CDN 節點相比於Server距離使用者更近,有著更短的網路延時。在 CDN 節點上將可快取的頁面靜態部分先快速返回給使用者,同時在 CDN 節點上發起動態部分內容請求,並將動態內容在靜態部分的響應流後繼續返回給使用者。

圖:ESR

03

收益衡量

速度是應用效能最直接體現。做效能收益衡量也需要多維度全方位的進行分析與對比。通過等量實驗組和對照組在核心指標方面大量真實資料的分位值對比,可以得到效能方面的收益,也可以關聯到使用者PV、UV以及收入等方面是資料收益。

監控網站真實使用者可感知的白屏、首屏、可互動等使用者體驗指標,從伺服器端響應時間、網路延時、DOM解析等細緻指標的變化也可以做日常效能優化。

  • 統計核心指標不同分位數的佔比資料。

  • 統計不同版本瀏覽器和裝置型別的核心指標資料,基於多平臺瀏覽器效能分析。

  • 統計不同區域(包括國家、省份、城市)、不同運營商以及接入方式(包括2G/3G/4G/WiFi)下的各關鍵網路效能指標。

圖:效能平臺

業內不錯的效能監控平臺包括ONEAPM、聽雲、效能魔方等,各個大公司和雲平臺也都提供不錯的相關監控服務。

04

總結

你做事的時候不只是靠經驗教訓的歷史積累,還有一套系統的流程或者模板。做效能優化是一件需要具有閉環思維的事情,特別是這種端到端的優化要注意事前規劃、事中執行和事後總結三個階段,而且還要結合不同的業務場景進行優化,有時候還要與客戶端相協同,並不是生拉硬套就可以完成的事情。

甚至很多大廠的業務前端還要一邊解決歷史包袱,一邊進行優化,小心前行!隨著優化後業務仍然在不斷的迭代和發展,如何鞏固效能優化結果也是一件任重道遠持續投入的事情,掌握效能優化基本原理結合具有優秀效能結構設計或許是一種智慧的方法。

參考資料

  • Web 效能優化資源合集(持續更新) | 微談 Web 前端效能優化-https://naluduo.vip/Web-Performance-Optimization/reference/#%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7

  • 以使用者為中心的效能指標-https://web.dev/i18n/zh/user-centric-performance-metrics/

  • 使用window.performance分析web前端效能 - 五藝 - 部落格園-https://www.cnblogs.com/y896926473/articles/7466951.html

  • HOME · PWA 應用實戰-https://lavas-project.github.io/pwa-book/

  • 網頁渲染流程詳解-https://www.jianshu.com/p/7659d714a642

  • 詳解web快取-https://zhuanlan.zhihu.com/p/90507417

掃描下方二維碼新增 「好未來技術」 微信官方賬號

進入好未來技術官方交流群與作者實時互動~

(若掃碼無效,可通過微訊號 TAL-111111 直接新增)

- 也許你還想看 -

Yarn 混合部署方案在好未來的實現

Web前端安全深度解讀

掰開揉碎系列|詳解Redis的Sorted-Set底層

我知道你“在看”喲~