基于RUM的前端優(yōu)化理論與實踐-性能篇

前言
對于前端來說,最重要的是體驗,而在前端體驗中,最為核心的就是性能。
前端頁面性能關(guān)鍵指標(biāo)的規(guī)范和計算規(guī)則。
如何看懂RUM可視化圖表?并通過圖表數(shù)據(jù)進(jìn)行項目優(yōu)化?
頁面性能指標(biāo)有哪些?
網(wǎng)絡(luò)連接瀑布圖(TL;DR)


與這張圖一一對應(yīng)的,是瀏覽器里面的`performance.timing`屬性,我們將其同時打印出來,做一個數(shù)據(jù)的對比說明。

navigationStart:表示從上一個文檔卸載結(jié)束時的unix時間戳,如果沒有上一個文檔,這個值將和fetchStart相等。
unloadEventStart:表示前一個網(wǎng)頁(與當(dāng)前頁面同域)unload的時間戳,如果無前一個網(wǎng)頁unload或者前一個網(wǎng)頁與當(dāng)前頁面不同域,則值為0。
unloadEventEnd:返回前一個頁面unload時間綁定的回調(diào)函數(shù)執(zhí)行完畢的時間戳。
redirectStart:第一個HTTP重定向發(fā)生時的時間。有跳轉(zhuǎn)且是同域名內(nèi)的重定向才算,否則值為0。
redirectEnd:最后一個HTTP重定向完成時的時間。有跳轉(zhuǎn)且是同域名內(nèi)部的重定向才算,否則值為0。
fetchStart:瀏覽器準(zhǔn)備好使用HTTP請求抓取文檔的時間,這發(fā)生在檢查本地緩存之前。
domainLookupStart/domainLookupEnd:DNS域名查詢開始/結(jié)束的時間,如果使用了本地緩存(即無DNS查詢)或持久連接,則與fetchStart值相等。
connectStart:HTTP(TCP)開始/重新 建立連接的時間,如果是持久連接,則與fetchStart值相等。
connectEnd:HTTP(TCP) 完成建立連接的時間(完成握手),如果是持久連接,則與fetchStart值相等。
secureConnectionStart:HTTPS連接開始的時間,如果不是安全連接,則值為0。
requestStart:HTTP請求讀取真實文檔開始的時間(完成建立連接),包括從本地讀取緩存。
responseStart:HTTP開始接收響應(yīng)的時間(獲取到第一個字節(jié)),包括從本地讀取緩存。
responseEnd:HTTP響應(yīng)全部接收完成的時間(獲取到最后一個字節(jié)),包括從本地讀取緩存。
domLoading:開始解析渲染DOM樹的時間,此時 Document.readyState變?yōu)閘oading,并將拋出readystatechange相關(guān)事件。
domInteractive:完成解析DOM樹的時間,Document.readyState變?yōu)閕nteractive,并將拋出readystatechange相關(guān)事件,注意只是DOM樹解析完成,這時候并沒有開始加載網(wǎng)頁內(nèi)的資源。
domContentLoadedEventStart:DOM解析完成后,網(wǎng)頁內(nèi)資源加載開始的時間,在DOMContentLoaded事件拋出前發(fā)生。
domContentLoadedEventEnd:DOM解析完成后,網(wǎng)頁內(nèi)資源加載完成的時間(如JS腳本加載執(zhí)行完畢)。
domComplete::DOM樹解析完成,且資源也準(zhǔn)備就緒的時間,Document.readyState變?yōu)閏omplete,并將拋出readystatechange相關(guān)事件。
loadEventStart:load事件發(fā)送給文檔,也即load回調(diào)函數(shù)開始執(zhí)行的時間。
loadEventEnd:load事件的回調(diào)函數(shù)執(zhí)行完畢的時間。
// 計算加載時間getPerformanceTiming() {const t = performance.timing;const times = {};// 頁面加載完成的時間,用戶等待頁面可用的時間times.loadPage = t.loadEventEnd - t.navigationStart;// 解析 DOM 樹結(jié)構(gòu)的時間times.domReady = t.domComplete - t.responseEnd;// 重定向的時間times.redirect = t.redirectEnd - t.redirectStart;// DNS 查詢時間times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;// 讀取頁面第一個字節(jié)的時間times.ttfb = t.responseStart - t.navigationStart;// 資源請求加載完成的時間times.request = t.responseEnd - t.requestStart;// 執(zhí)行 onload 回調(diào)函數(shù)的時間times.loadEvent = t.loadEventEnd - t.loadEventStart;// DNS 緩存時間times.appcache = t.domainLookupStart - t.fetchStart;// 卸載頁面的時間times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;// TCP 建立連接完成握手的時間times.connect = t.connectEnd - t.connectStart;return times;}
?
RUM使用了哪些性能指標(biāo)?
頁面加載瀑布圖

Aegis SDK源碼中對其的計算規(guī)則如下:
const t: PerformanceTiming = performance.timing;if (!t) return;這里不知道為什么有時候 t.loadEventStart - t.domInteractive 返回一個很大的負(fù)數(shù),暫時先簡單處理let resourceDownload = t.loadEventStart - t.domInteractive;if (resourceDownload < 0) resourceDownload = 1070;result = {dnsLookup: t.domainLookupEnd - t.domainLookupStart,tcp: t.connectEnd - t.connectStart,ssl: t.secureConnectionStart === 0 ? 0 : t.requestStart - t.secureConnectionStart,ttfb: t.responseStart - t.requestStart,contentDownload: t.responseEnd - t.responseStart,domParse: t.domInteractive - t.domLoading,resourceDownload};
默認(rèn)通過MutationObserver這個API來監(jiān)控瀏覽器document對象的DOM變化,只計算在首屏內(nèi)的DOM元素,把DOM變化時間作為x軸,單位時間內(nèi)DOM變化的數(shù)量作為y軸,繪制曲線后,我們找到DOM變化最高點,認(rèn)為是首屏完成。
如果開發(fā)者覺得該算法不準(zhǔn)確,希望自己標(biāo)記DOM元素,可以添加屬性<div AEGIS-FIRST-SCREEN-TIMING></div>,把某個元素識別為首屏關(guān)鍵元素,SDK認(rèn)為只要用戶首屏出現(xiàn)此元素就是首屏完成。也可以添加屬性<div AEGIS-IGNORE-FIRST-SCREEN-TIMING></div>,把該DOM列入黑名單。
首字節(jié)(TTFB)=DNS+SSL+TCP+TTFB
DOMReady=DNS+SSL+TCP+TTFB+ContentDownload+DomParse
頁面完全加載=DNS+SSL+TCP+TTFB+ContentDownload+ DomParse+ResourceDownload
良好網(wǎng)站的基本指標(biāo)-Web Vitals
上述計算首屏的算法是Aegis SDK自主提供的算法,用戶場景千變?nèi)f化,無法覆蓋所有場景,而且這個算法也無法得到所有開發(fā)者的認(rèn)同。這個時候就需要祭出Web Vitals了。
什么是Web Vitals?

CLS(Cumulative Layout Shift-累積布局移位):CLS會衡量在網(wǎng)頁的整個生命周期內(nèi)發(fā)生的所有意外布局偏移的得分總和。得分是零到任意正數(shù),其中0表示無偏移,且數(shù)字越大,網(wǎng)頁的布局偏移越大。
FCP(First Contentful Paint-首次內(nèi)容繪制):FCP度量從頁面開始加載到頁面內(nèi)容的任何部分呈現(xiàn)在屏幕上的時間,頁面內(nèi)容包括文本、圖像(包括背景圖像)、<svg>元素或非白色的<canvas>元素。
FID(First Input Delay-首次輸入延遲):從用戶首次與您的網(wǎng)頁互動(點擊鏈接、點按按鈕,等等)到瀏覽器響應(yīng)此次互動之間的用時。這種衡量方案的對象是被用戶首次點擊的任何互動式元素。
LCP(Largest Contentful Paint-最大內(nèi)容繪制):LCP度量從用戶請求網(wǎng)址到在視口中渲染最大可見內(nèi)容元素所需的時間。最大的元素通常是圖片或視頻,也可能是大型塊級文本元素。
TTFB (Time To First Byte-從服務(wù)器接收到第一個字節(jié)耗時) :TTFB 是發(fā)出頁面請求到接收到應(yīng)答數(shù)據(jù)第一個字節(jié)的時間總和,它包含了DNS解析時間、TCP連接時間、發(fā)送HTTP請求時間和獲得響應(yīng)消息第一個字節(jié)的時間。

如何分析性能數(shù)據(jù)&指導(dǎo)開發(fā)優(yōu)化?
擁有了RUM這個好用的工具,下面就可以用數(shù)據(jù)指導(dǎo)開發(fā)和決策了。
優(yōu)化舉例





資源加載優(yōu)化
拆包,通過把公共外部依賴打包成為vendor,并且對組件做異步加載。
去掉一些非必需的包,比如用戶引入了全量的lodash,讓其改成lodash-es,方便webpack做treeshaking;去掉僅為了把某個時間做格式化而引入的moment;去掉jquery,而當(dāng)初引入jquery僅僅為了查詢某個元素而,真是得不償失。
建議使用webpack-bundle-analyzer對打包后的代碼進(jìn)行分析,查看哪些包不需要引用,或者可以單獨打包。
網(wǎng)絡(luò)協(xié)議方面全面引入HTTP2,合并了一些小的靜態(tài)資源,把一些小的svg改成了base64。

頁面主進(jìn)程阻塞嚴(yán)重,Aegis SDK的一些邏輯在執(zhí)行的時候受到了影響,導(dǎo)致實際執(zhí)行時間要晚于設(shè)定的時間,所以上報的“首屏耗時”其實要比實際晚的。
用戶的頁面會在首屏完成后,繼續(xù)加載很多DOM元素,也就是有很多DOM元素的變化,導(dǎo)致了Aegis SDK計算出來的首屏?xí)r間也要晚于真實的“首屏?xí)r間”。

CLS指標(biāo)優(yōu)化
在一開始就確定列表高度(加入分頁),通過骨架屏優(yōu)化加載效果,同時減少DOM變化。
廣告掛件使用絕對布局,使其脫離文檔流,減少DOM變化。
一些其他元素,如圖片等,確定長度和寬度屬性,這些值允許瀏覽器在將圖像渲染到位之前保留視覺空間。
一些元素的變化,通過CSS實現(xiàn),而不是使用JS改變元素屬性實現(xiàn)。


總結(jié)

作者簡介
李振
騰訊云高級工程師
李振,騰訊云高級工程師,騰訊內(nèi)部最受歡迎的前端開源項目TAM負(fù)責(zé)人,騰訊云前端性能監(jiān)控RUM負(fù)責(zé)人,有豐富的前端開發(fā)和產(chǎn)品研發(fā)經(jīng)驗。

點擊「閱讀原文」,立即體驗RUM~

