Web前端性能(néng)優化深度解讀
導讀: 用戶體驗是web産品非常重要的部分,核心是讓用戶使用舒服,幫助用戶流暢地得到所求,用戶體驗的優劣甚至會(huì)影響到用戶的留存。體驗差的網站各有各的不同,但是體驗好(hǎo)的網站往往都(dōu)有一些共性,這(zhè)些優秀的特征凝結了設計師、研發(fā)工程師和産品經(jīng)理的大量智慧。
訪問交互速度迅速
動畫效果順滑流暢
有用戶操作的反饋
簡單的操作步驟
整站體驗一緻性
主體内容在最顯眼的位置
無障礙訪問,不同的人群均可使用
在這(zhè)些優秀體驗的特性中,最容易讓人産生共鳴的往往是網站的性能(néng)問題,比如網站的訪問交互速度。如何發(fā)現性能(néng)問題?性能(néng)如何優化(性能(néng)優化的常規方法和框架方法)?如何衡量收益?本文根據多年在性能(néng)優化方面(miàn)的實踐,著(zhe)重分享一下首屏性能(néng)優化的一些經(jīng)驗。
01 性能(néng)采集
工欲利器事(shì),必先利其器。我們所說(shuō)的性能(néng)采集并不是性能(néng)分析Devtools,而是指在産品真實用戶訪問的大數據中進(jìn)行抽樣,對(duì)于抽樣用戶進(jìn)行性能(néng)數據采集,得到真實用戶環境下産品性能(néng)數據。各浏覽器廠商都(dōu)已認識到性能(néng)對(duì)于web開(kāi)發(fā)的重要性,爲了解決當前性能(néng)測試的困難,W3C推出了一套性能(néng)API标準,目的是簡化開(kāi)發(fā)者對(duì)網站性能(néng)進(jìn)行精确分析與控制的過(guò)程,方便開(kāi)發(fā)者采取手段提高web性能(néng)。整套标準包含了10餘種(zhǒng)API,在下圖中可以看到它們當前在規範流程中的進(jìn)展。
圖:性能(néng)API标準(摘錄51CTO圖片)
這(zhè)套标準中提供了導航定時(shí)(Navigation Timing)、資源定時(shí)(Resource Timing)、用戶定時(shí)User Timing和性能(néng)時(shí)間線(Performance Timeline)規範可以幫助開(kāi)發(fā)人員精确地測量文檔的導航時(shí)間,在頁面(miàn)上獲取資源的情況,以及開(kāi)發(fā)人員腳本執行情況。
在這(zhè)套API中,頁面(miàn)加載Navigation Timing和頁面(miàn)資源加載Resource Timing這(zhè)兩(liǎng)個API可以幫助我們獲取頁面(miàn)的Domready時(shí)間、onload時(shí)間、白屏時(shí)間以及單個頁面(miàn)資源在從發(fā)送請求到獲取到response各階段例如帶寬、延遲或主頁的整體頁面(miàn)加載時(shí)間的性能(néng)參數,這(zhè)些都(dōu)是基于真實用戶數據(RUM)。
圖:Navigation Timing關系圖(摘錄W3C)
在獲取用戶訪問Timing數據的前提下,我們可以結合具體業務場景定義訪問性能(néng)的核心指标,例如白屏時(shí)間、首屏時(shí)間FSP、用戶可交互時(shí)間TTI、頁面(miàn)onload時(shí)間等作爲核心優化指标,其中首屏時(shí)間和用戶可交互時(shí)間需要單獨埋點自定義。
還(hái)可以通過(guò)獲取DNS查詢耗時(shí)、TCP鏈接耗時(shí)、request請求耗時(shí)、解析dom樹耗時(shí)、白屏時(shí)間、domready時(shí)間、onload時(shí)間等做性能(néng)分析,後(hòu)續根據症狀對(duì)這(zhè)些細緻階段做性能(néng)優化,這(zhè)些參數是通過(guò)上面(miàn)的performance.timing各個屬性的差值組成(chéng)的。
通過(guò)使用API對(duì)各個階段性能(néng)指标進(jìn)行采集,等待到所有數據都(dōu)獲取完成(chéng)之後(hòu),通過(guò)網絡請求將(jiāng)數據發(fā)送到服務器用作後(hòu)續數據分析使用。
02 性能(néng)優化
快速加載、及時(shí)響應用戶反饋、提供流暢的動畫、以及擁有類似原生APP一般沉浸的用戶體驗是web應用在性能(néng)優化上的目标,這(zhè)主要關系到加載性能(néng)和渲染性能(néng)兩(liǎng)個方面(miàn),本章節介紹一些常規優化方法和框架級優化方案。
2.1 加載性能(néng)優化
Web 頁面(miàn)通常由 HTML、CSS、JavaScript 和其他多媒體資源組成(chéng),充斥著(zhe)各種(zhǒng)同步資源和異步資源。頁面(miàn)加載時(shí),必須從服務器獲取這(zhè)些資源。
2.1.1 減小資源體積
壓縮文本内容
優化JavaScript第三方庫引入
壓縮雖然簡單,但十分有效,這(zhè)也是最廣泛的優化資源體積的操作。許多工具可以幫助我們完成(chéng)HTML、CSS、JavaScript、圖片等壓縮。例如,TerserPlugin可以用于壓縮 JavaScript,PostCSS可以對(duì) CSS 進(jìn)行壓縮,以及完成(chéng)前綴自動補全工作。除了壓縮單個文件外,在服務器上配置 Gzip 也十分重要。Gzip 對(duì)文本資源的壓縮效果非常明顯,通常可以將(jiāng)體積再壓縮至原本的 30% 左右,但 Gzip 對(duì)已經(jīng)單獨壓縮的圖像等非文本資源來說(shuō),效果并不好(hǎo)。
如果我們隻需要使用工具庫中少數幾個簡單函數,可以考慮使用原生 JavaScript 代替。不計後(hòu)果地引入第三方庫,會(huì)迅速增大 JavaScript 資源的體積。
2.1.2 對(duì)資源進(jìn)行緩存
緩存在優化頁面(miàn)加載性能(néng)的工作中有舉足輕重的作用,緩存無處不在,包括浏覽器端、網絡代理、服務端緩存,往往能(néng)大幅加快響應速度。
圖:web全鏈路緩存
HTTP 緩存
Local Storage
Cache Storage
IndexedDB
CDN
現代浏覽器都(dōu)實現了 HTTP 緩存機制。浏覽器在初次獲取資源後(hòu),會(huì)根據 HTTP 響應頭部的Cache-Control和ETag字段,來決定該資源的強緩存策略或者協商緩存策略。
Local Storage主要是用來作爲本地存儲來使用的,解決了cookie存儲空間不足的問題(cookie中每條cookie的存儲空間爲4k),localStorage中一般浏覽器支持的是5M大小。
Cache Storage它用來存儲 Response 對(duì)象的,也就(jiù)是說(shuō)用來對(duì) HTTP響應做緩存的,通常在PWA技術中使用。
IndexedDB是一種(zhǒng)在浏覽器中持久存儲數據的方法,允許我們不考慮網絡可用性,創建具有豐富查詢能(néng)力的可離線web應用程序。
内容緩存在CDN網絡節點,位于用戶接入點,是面(miàn)向(xiàng)最終用戶的内容提供設備,可緩存靜态Web内容和流媒體内容,實現内容的邊緣傳播和存儲,以便用戶的就(jiù)近訪問。
2.1.3 調整資源優先級
通過(guò)調整資源加載優先級,保證主體内容能(néng)夠較快的被加載完成(chéng),通過(guò)預加載、懶加載等多種(zhǒng)方式,調整資源加載的行爲,優化網頁加載性能(néng)。
預加載
預連接與 DNS 預解析
預取
懶加載
Service Worker
通過(guò)來提前聲明當前頁面(miàn)所需的資源,以便浏覽器能(néng)預加載這(zhè)些資源。通過(guò)media屬性進(jìn)行媒體查詢,根據響應式的情況選擇性地預加載資源。
預連接會(huì)提前完成(chéng) DNS 解析、TCP 握手和 TLS 協商的工作,但并不會(huì)提前加載資源。也可以考慮使用,提前與資源建立 socket 連接。
浏覽器會(huì)在空閑時(shí),使用最低優先級下載預取的資源。預取通過(guò)聲明,通常用于點擊“下一頁”的頁面(miàn)動作之前提前加載用戶接下來可能(néng)需要的html資源。
按需加載和延時(shí)加載都(dōu)屬于懶加載的範疇,例如對(duì)圖像資源采用“懶加載”策略,即僅加載當前在視口内的圖像,對(duì)于視口外未加載的圖像,在其即將(jiāng)滾動進(jìn)入視口時(shí)才開(kāi)始加載。
利用Service Worker 線程脫離在主線程之外來進(jìn)行 Web 資源和請求的持久離線緩存。
2.1.4 合理拆分代碼
浏覽器支持并行加載資源,合理拆分資源也是一種(zhǒng)有效的優化方法。爲了更好(hǎo)的效果,我們往往不需要在首屏一次性加載所有 JavaScript 代碼,合理的拆分代碼、區分開(kāi)發(fā)和生産環境使用少量主要代碼,將(jiāng)當前暫時(shí)不需要的代碼拆分出去可以有效加快首屏展現的速度。通過(guò)webpack區分開(kāi)發(fā)環境和生産環境差異化配置打包資源可以有效優化代碼,Tree shaking使得模塊間依賴可以通過(guò)靜态分析來更好(hǎo)地優化剪枝(僅ES modules支持)。webpack-bundle-analyzer 是一個關于 webpack 構建産物的可視化插件,可以清晰地看到構建産物的體積,幫助分析後(hòu)續的優化方向(xiàng)。
2.1.5 HTTP/2
HTTP/2帶給WEB帶來了很大的性能(néng)提升,同時(shí)多路複用、頭部壓縮、Server Push等特點,使得可以在一個連接上同時(shí)打開(kāi)多個流雙向(xiàng)傳輸數據,服務端可以在發(fā)送頁面(miàn) HTML 時(shí)主動推送其它資源,而不用等到浏覽器解析到相應位置,發(fā)起(qǐ)請求再響應。
圖:http1 vs http2
2.2 渲染性能(néng)優化
浏覽器在渲染頁面(miàn)前,首先會(huì)將(jiāng) HTML 文本内容解析爲 DOM,將(jiāng) CSS 解析爲 CSSOM。DOM 和 CSSOM 都(dōu)是樹狀數據結構,兩(liǎng)者相互獨立,但又有相似之處。接著(zhe),浏覽器會(huì)將(jiāng) DOM 和 CSSOM 樹合并成(chéng)渲染樹。從 DOM 樹的根節點開(kāi)始遍曆,并在 CSSOM 樹中查找節點對(duì)應的樣式規則,合并成(chéng)渲染樹中的節點。在遍曆的過(guò)程中,不可見的節點將(jiāng)會(huì)被忽略。渲染樹随後(hòu)會(huì)被用于布局,就(jiù)是計算渲染樹節點在浏覽器視口中确切的位置和大小。浏覽器進(jìn)行一次布局的性能(néng)開(kāi)銷較大,我們需要小心地避免頻繁觸發(fā)頁面(miàn)重新布局。得到渲染樹節點的幾何布局信息後(hòu),浏覽器就(jiù)可以將(jiāng)節點繪制到屏幕上了,包括繪制文本、顔色、邊框和陰影等。
繪制的過(guò)程,首先會(huì)根據布局和視覺相關的樣式信息生成(chéng)一系列繪制操作,随後(hòu)執行栅格化(栅格化是將(jiāng)向(xiàng)量圖形格式表示的圖像轉換成(chéng)位圖以用于顯示器或者打印機輸出的過(guò)程),將(jiāng)待繪制項轉換爲位圖存儲在 GPU 中,最終通過(guò)圖形庫將(jiāng)像素繪制在屏幕上。
圖:浏覽器渲染過(guò)程
頁面(miàn)不是一次性被繪制出來的。實際上,頁面(miàn)被分成(chéng)了多個圖層進(jìn)行繪制,這(zhè)些圖層會(huì)在另一個單獨的線程裡(lǐ)繪制到屏幕上,這(zhè)個過(guò)程被稱作合成(chéng)。合成(chéng)線程可以對(duì)圖層進(jìn)行剪切、變換等處理,因此可以用于響應用戶基本的滾動、縮放等操作,又不會(huì)受到主線程阻塞的影響。
2.2.1 關鍵渲染路徑
由于渲染都(dōu)是在主進(jìn)程中執行的,所以合理的利用主進(jìn)程渲染非常重要。首屏渲染所必須的關鍵資源,共同組成(chéng)了關鍵渲染路徑,減少非關鍵渲染路徑的資源消耗可以有效提升渲染速度。
延遲非關鍵 CSS 加載
async 和 defer
Web 應用中往往會(huì)有一些首屏渲染時(shí)用不到的 CSS,如彈框的樣式等。通過(guò)引用的 CSS 都(dōu)會(huì)在加載時(shí)阻塞頁面(miàn)渲染。爲了使這(zhè)些非關鍵 CSS 不阻塞頁面(miàn)渲染,可以通過(guò)拆分資源的方式并延遲非關鍵資源加載。
由于渲染都(dōu)是在主進(jìn)程中執行的,所以合理的利用主進(jìn)程渲染非常重要。首屏渲染所必須的關鍵資源,共同組成(chéng)了關鍵渲染路徑,減少非關鍵渲染路徑的資源消耗可以有效提升渲染速度。
2.2.2 非阻塞 JavaScript
用戶對(duì)于不流暢的滾動或動畫十分敏感,一般要求頁面(miàn)幀率應達到每秒 60 幀。由于 JavaScript 一般是單線程執行的,長(cháng)時(shí)間執行的任務會(huì)阻塞浏覽器的主線程,使頁面(miàn)失去響應,出現卡頓和假死的現象。
頁面(miàn)滾動
requestAnimationFrame 任務在浏覽器渲染下一幀之前執行
requestIdleCallback 將(jiāng)任務安排在浏覽器空閑時(shí)執行
Web Workers
當我們監聽 touchstart、touchmove 等事(shì)件時(shí),由于合成(chéng)線程并不知道(dào)我們是否會(huì)通過(guò) event.preventDefault() 來阻止默認的滾動行爲,從而在每次事(shì)件觸發(fā)時(shí),都(dōu)會(huì)等待事(shì)件處理函數執行完畢後(hòu)再進(jìn)行頁面(miàn)滾動。這(zhè)通常會(huì)導緻較明顯的延遲,影響頁面(miàn)滾動的流暢性。通過(guò)在addEventListener()時(shí)聲明{passive: true},來表明事(shì)件處理函數不會(huì)阻止頁面(miàn)滾動,使得用戶的操作更快得到響應。
我們可以將(jiāng)一些耗性能(néng)的邏輯放在 worker 線程中進(jìn)行處理,這(zhè)樣主線程就(jiù)能(néng)繼續響應用戶操作和渲染頁面(miàn)了。
2.2.3 降低渲染樹計算複雜性
結構越複雜的頁面(miàn)往往性能(néng)越差,動畫多的頁面(miàn)出現卡頓的幾率也越大。
減少查找與元素匹配成(chéng)本
減少布局次數
優化繪制與合成(chéng)
渲染樹由 DOM 和 CSSOM 樹合并而成(chéng),對(duì)于每個 DOM 元素,需要查找與元素匹配的樣式規則。CSS Modules 是一種(zhǒng)較爲主流的 CSS-in-JS 解決方案,利用 webpack 等構建工具,可以對(duì)類選擇器生成(chéng)自定義格式的唯一類名,同樣能(néng)減少浏覽器匹配 CSS 選擇器的開(kāi)銷。
浏覽器進(jìn)行一次布局的開(kāi)銷很大,所以我們需要盡可能(néng)避免直接修改這(zhè)些屬性,尤其是不應將(jiāng)布局屬性用于動畫效果,否則會(huì)出現明顯的掉幀現象。
修改絕大多數樣式屬性都(dōu)會(huì)導緻頁面(miàn)重繪,這(zhè)很難避免。僅有的例外是transform和opacity,這(zhè)是由于它們可以僅由合成(chéng)器操作圖層來實現。transform和opacity非常适合用于實現動畫效果,但我們仍需要通過(guò)will-change爲它們創建獨立的圖層,避免影響其他圖層的繪制。
2.3 框架優化方法
CSR、SSR、NSR、ESR、hybrid離線包、Big pipe、app cache等,都(dōu)是不錯的方法。
2.3.1 CSR(Client Side Render)
浏覽器渲染顧名思義就(jiù)是所有的頁面(miàn)渲染、邏輯處理、頁面(miàn)路由、接口請求均是在浏覽器中發(fā)生,也就(jiù)是從服務端請求一個簡單HTML文件然後(hòu)通過(guò)執行JavaScript在HTML上進(jìn)行内容的添加。其實,現代主流的前端框架均是這(zhè)種(zhǒng)渲染方式,這(zhè)種(zhǒng)渲染方式的好(hǎo)處在于實現了前後(hòu)端架構分離,利于前後(hòu)端職責分離,并且能(néng)夠首次渲染迅速有效減少白屏時(shí)間。同時(shí),CSR可以通過(guò)在打包編譯階段進(jìn)行預渲染或者骨架屏生成(chéng),可以進(jìn)一步提升首次渲染的用戶體驗。
圖:CSR
2.3.2 SSR(Server Side Render)
服務端渲染則是在服務端完成(chéng)頁面(miàn)的渲染,在服務端完成(chéng)頁面(miàn)模闆、數據填充、頁面(miàn)渲染,然後(hòu)將(jiāng)完整的HTML内容返回給到浏覽器。由于所有的渲染工作都(dōu)在服務端完成(chéng),因此網站的首屏時(shí)間和TTI都(dōu)會(huì)表現比較好(hǎo)。
圖:SSR
但是,渲染需要在服務端完成(chéng),并不能(néng)很好(hǎo)進(jìn)行前後(hòu)端職責分離,而且白屏時(shí)間也會(huì)比較長(cháng),同時(shí),對(duì)于服務端的負載要求也會(huì)比較高。
2.3.3 NSR(Native Side Render)
GMTC2019 全球大前端技術上 UC 團隊提到了 0.3 秒的 “閃開(kāi)” 方案。這(zhè)種(zhǒng)方案适用于混合開(kāi)發(fā),NSR本質是分布式SSR,通過(guò)加載離線頁面(miàn)模闆,Ajax預加載頁面(miàn)數據,Native渲染生成(chéng)Html數據并且緩存在客戶端,將(jiāng)服務器的渲染工作放在了一個個獨立的移動設備中,實現了頁面(miàn)的預加載,同時(shí)又不會(huì)增加額外的服務器壓力。核心思路是借助浏覽器啓用一個 JS-Runtime,提前將(jiāng)下載好(hǎo)的 html 模闆及預取的 feed 流數據進(jìn)行渲染,然後(hòu)將(jiāng) html 設置到内存級别的 MemoryCache 中,從而達到點開(kāi)即看的效果。
圖:NSR
2.3.4 ESR(Edge Side Render)
邊緣渲染的核心思想是,借助邊緣計算的能(néng)力,將(jiāng)靜态内容與動态内容以流式的方式,先後(hòu)返回給用戶。CDN 節點相比于Server距離用戶更近,有著(zhe)更短的網絡延時(shí)。在 CDN 節點上將(jiāng)可緩存的頁面(miàn)靜态部分先快速返回給用戶,同時(shí)在 CDN 節點上發(fā)起(qǐ)動态部分内容請求,并將(jiāng)動态内容在靜态部分的響應流後(hòu)繼續返回給用戶。
圖:ESR
03、收益衡量
速度是應用性能(néng)最直接體現。做性能(néng)收益衡量也需要多維度全方位的進(jìn)行分析與對(duì)比。通過(guò)等量實驗組和對(duì)照組在核心指标方面(miàn)大量真實數據的分位值對(duì)比,可以得到性能(néng)方面(miàn)的收益,也可以關聯到用戶PV、UV以及收入等方面(miàn)是數據收益。
監控網站真實用戶可感知的白屏、首屏、可交互等用戶體驗指标,從服務器端響應時(shí)間、網絡延時(shí)、DOM解析等細緻指标的變化也可以做日常性能(néng)優化。
統計核心指标不同分位數的占比數據。
統計不同版本浏覽器和設備類型的核心指标數據,基于多平台浏覽器性能(néng)分析。
統計不同區域(包括國(guó)家、省份、城市)、不同運營商以及接入方式(包括2G/3G/4G/WiFi)下的各關鍵網絡性能(néng)指标。
圖:性能(néng)平台
業内不錯的性能(néng)監控平台包括ONEAPM、聽雲、性能(néng)魔方等,各個大公司和雲平台也都(dōu)提供不錯的相關監控服務。
04 總結
你做事(shì)的時(shí)候不隻是靠經(jīng)驗教訓的曆史積累,還(hái)有一套系統的流程或者模闆。做性能(néng)優化是一件需要具有閉環思維的事(shì)情,特别是這(zhè)種(zhǒng)端到端的優化要注意事(shì)前規劃、事(shì)中執行和事(shì)後(hòu)總結三個階段,而且還(hái)要結合不同的業務場景進(jìn)行優化,有時(shí)候還(hái)要與客戶端相協同,并不是生拉硬套就(jiù)可以完成(chéng)的事(shì)情。
甚至很多大廠的業務前端還(hái)要一邊解決曆史包袱,一邊進(jìn)行優化,小心前行!随著(zhe)優化後(hòu)業務仍然在不斷的叠代和發(fā)展,如何鞏固性能(néng)優化結果也是一件任重道(dào)遠持續投入的事(shì)情,掌握性能(néng)優化基本原理結合具有優秀性能(néng)結構設計或許是一種(zhǒng)智慧的方法。