網(wǎng)站地圖 seo/武漢百度關(guān)鍵詞推廣
性能優(yōu)化 - 前端性能監(jiān)控和性能指標(biāo)計(jì)算方式
- 前言
- 一. 性能指標(biāo)介紹
- 1.1 單一指標(biāo)介紹
- 1.2 指標(biāo)計(jì)算
- ① Redirect(重定向耗時(shí))
- ② AppCache(應(yīng)用程序緩存的DNS解析)
- ③ DNS(DNS解析耗時(shí))
- ④ TCP(TCP連接耗時(shí))
- ⑤ TTFB(請(qǐng)求響應(yīng)耗時(shí))
- ⑥ Trans(內(nèi)容傳輸耗時(shí))
- ⑦ DOM(DOM解析耗時(shí))
- 1.3 FP(first-paint) 和 FCP(first-contentful-paint)
- 1.4 LCP(Largest Contentful Paint)
- 1.5 LongTask長(zhǎng)任務(wù)統(tǒng)計(jì)
- 二. 性能指標(biāo)計(jì)算測(cè)試
- 2.1 衡量網(wǎng)絡(luò)請(qǐng)求響應(yīng)時(shí)間的指標(biāo)
- 2.2 衡量頁面加載速度的指標(biāo)
- 2.3 TTI(Time to Interactive)衡量頁面可交互性的指標(biāo)
- 2.4 TBT(Total Blocking Time)
- 2.5 總結(jié)
前言
利用LightHouse進(jìn)行合理的頁面性能優(yōu)化 這篇文章主要講解了如何使用Lighthouse
。 這里把相關(guān)圖片再展示一下:
我們可以看到Lighthouse
計(jì)算的時(shí)候,會(huì)根據(jù)這幾個(gè)維度的指標(biāo)來計(jì)算總分。那么本篇文章,就主要講解下前端性能監(jiān)控相關(guān)的重要指標(biāo)含義和計(jì)算方式。
一. 性能指標(biāo)介紹
在介紹指標(biāo)之前,我們首先應(yīng)當(dāng)知道這些數(shù)據(jù)可以從哪里獲取。JS
里面,有一個(gè) performance
對(duì)象,它是專門用來用于性能監(jiān)控的對(duì)象,內(nèi)置了一些前端需要的性能參數(shù)。
我們隨便打開一個(gè)瀏覽器,在終端控制臺(tái)輸入以下內(nèi)容:
performance.getEntriesByType('navigation')
如圖:
1.1 單一指標(biāo)介紹
navigationStart
:導(dǎo)航開始的時(shí)間,即瀏覽器開始獲取頁面的時(shí)間。redirectCount
:重定向次數(shù),表示在導(dǎo)航過程中發(fā)生的重定向次數(shù)。type
:導(dǎo)航類型,可能的取值有:navigate
:常規(guī)導(dǎo)航,例如用戶點(diǎn)擊鏈接或輸入U(xiǎn)RL進(jìn)行的導(dǎo)航。reload
:頁面重新加載。back_forward
:通過瀏覽器的前進(jìn)或后退按鈕導(dǎo)航。
unloadEventStart
:前一個(gè)頁面的unload
事件開始的時(shí)間。unloadEventEnd
:前一個(gè)頁面的unload
事件結(jié)束的時(shí)間。redirectStart
:重定向開始的時(shí)間。redirectEnd
:重定向結(jié)束的時(shí)間。fetchStart
:瀏覽器開始獲取頁面資源的時(shí)間。domainLookupStart
:域名解析開始的時(shí)間。domainLookupEnd
:域名解析結(jié)束的時(shí)間。connectStart
:建立與服務(wù)器連接開始的時(shí)間。connectEnd
:建立與服務(wù)器連接結(jié)束的時(shí)間。secureConnectionStart
:安全連接開始的時(shí)間,如果不是安全連接,則該值為0。requestStart
:向服務(wù)器發(fā)送請(qǐng)求的時(shí)間。responseStart
:接收到服務(wù)器響應(yīng)的時(shí)間。responseEnd
:接收到服務(wù)器響應(yīng)并且所有資源都已接收完成的時(shí)間。domLoading
:開始解析文檔的時(shí)間。domInteractive
:文檔解析完成并且所有子資源(例如圖片、樣式表等)也已加載完成的時(shí)間。domContentLoadedEventStart
:DOMContentLoaded
事件開始的時(shí)間,表示HTML文檔解析完成并且所有腳本文件已下載完成。domContentLoadedEventEnd
:DOMContentLoaded
事件結(jié)束的時(shí)間,表示所有腳本文件已執(zhí)行完成。domComplete
:文檔和所有子資源(例如圖片、樣式表等)都已完成加載的時(shí)間。loadEventStart
:load
事件開始的時(shí)間,表示所有資源(包括圖片、樣式表、腳本文件等)都已加載完成。loadEventEnd
:load
事件結(jié)束的時(shí)間,表示所有資源(包括圖片、樣式表、腳本文件等)都已執(zhí)行完成。
1.2 指標(biāo)計(jì)算
我們看下圖
我們從上圖出發(fā),分別對(duì)各個(gè)階段進(jìn)行計(jì)算,我們說下幾個(gè)比較重要的階段,按照從左往右的順序。
① Redirect(重定向耗時(shí))
表示從重定向開始(redirectStart
)到重定向結(jié)束的時(shí)間(redirectEnd
)的時(shí)間間隔,它反映了瀏覽器在這段時(shí)間內(nèi)完成了重定向的過程:
const redirectTime = redirectEnd - redirectStart
② AppCache(應(yīng)用程序緩存的DNS解析)
這一部分也是在進(jìn)行DNS
解析,在使用AppCache
(應(yīng)用程序緩存)的情況下,瀏覽器會(huì)在加載頁面時(shí)檢查緩存中是否存在相應(yīng)的資源,并根據(jù)需要更新緩存:
const appcacheTime = domainLookupStart - fetchStart
③ DNS(DNS解析耗時(shí))
DNS
解析耗時(shí):在瀏覽器加載網(wǎng)頁時(shí),當(dāng)需要與服務(wù)器建立連接時(shí),瀏覽器會(huì)首先進(jìn)行DNS
解析,將域名轉(zhuǎn)換為對(duì)應(yīng)的IP
地址。DNS
解析的過程包括向DNS
服務(wù)器發(fā)送查詢請(qǐng)求、等待DNS
服務(wù)器響應(yīng)以及獲取到IP
地址。
dns = domainLookupEnd - domainLookupStart
④ TCP(TCP連接耗時(shí))
TCP
連接耗時(shí):在瀏覽器加載網(wǎng)頁時(shí),當(dāng)瀏覽器需要與服務(wù)器建立連接時(shí),它會(huì)向服務(wù)器發(fā)送請(qǐng)求,并等待服務(wù)器響應(yīng)。建立連接的過程包括TCP
握手、SSL
握手等。
tcp = connectEnd - connectStart
其中還有建立SSL
連接的時(shí)間,包括在TCP
耗時(shí)里面。
ssl = connectEnd - secureConnectionStart
⑤ TTFB(請(qǐng)求響應(yīng)耗時(shí))
請(qǐng)求耗時(shí):從發(fā)送請(qǐng)求到接收到服務(wù)器響應(yīng)的第一個(gè)字節(jié)所花費(fèi)的時(shí)間。
ttfb = responseStart - requestStart
⑥ Trans(內(nèi)容傳輸耗時(shí))
當(dāng)瀏覽器發(fā)送請(qǐng)求后,服務(wù)器會(huì)返回相應(yīng)的響應(yīng),這個(gè)差值就是衡量瀏覽器接收服務(wù)器響應(yīng)的耗時(shí)。
trans = responseEnd - responseStart
⑦ DOM(DOM解析耗時(shí))
DOM
這一塊比較復(fù)雜,實(shí)際上還能分成3個(gè)小DOM
階段。
- 階段一(注意,上圖中并沒有顯式地展示出來):解析
DOM
階段。
const dom1 = domInteractive - responseEnd
- 階段二:文檔解析完成,
html、js
解析完成,css
、圖片加載完成。即加載DOM
階段。
const dom2 = domComplete-domInteractive
- 階段二當(dāng)中還可以分出一小個(gè)階段:代表從開始加載
DOM
內(nèi)容到DOM
內(nèi)容加載完成的時(shí)間間隔。
const domLoaded = domContentLoadedEventEnd - domContentLoadedEventStart
1.3 FP(first-paint) 和 FCP(first-contentful-paint)
FP(first-paint)
和FCP(first-contentful-paint)
FP
指的是瀏覽器首次將像素渲染到屏幕上的時(shí)間點(diǎn),即頁面開始渲染的時(shí)間點(diǎn)。通常情況下,FP
是指瀏覽器首次繪制任何可見的內(nèi)容,包括背景色、文字、圖片等,但不包括用戶界面的控件,比如滾動(dòng)條、按鈕等。
FCP
指的是瀏覽器首次將頁面的有意義的內(nèi)容渲染到屏幕上的時(shí)間點(diǎn),即頁面開始呈現(xiàn)有意義的內(nèi)容的時(shí)間點(diǎn)。有意義的內(nèi)容可以是文本、圖片、視頻等,但不包括背景色、邊框等無意義的內(nèi)容。
一般情況下,兩者基本上沒有什么區(qū)別,來說下兩者的獲取方式:
const fp = performance.getEntriesByName('first-paint')[0].startTime
const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime
后面我們都只說FCP
。
1.4 LCP(Largest Contentful Paint)
LCP:Largest Contentful Paint
:它表示在頁面加載過程中,最大的可見內(nèi)容元素(例如圖片、視頻、文本塊等)加載完成并呈現(xiàn)在屏幕上的時(shí)間點(diǎn)。,是測(cè)量加載速度感知的重要指標(biāo)之一。
獲取方式,我們主要通過Performance
來進(jìn)行監(jiān)聽:
new PerformanceObserver((entryList) => {var maxSize = 0;var renderTime = 0;for (var entry of entryList.getEntries()) {// 渲染的內(nèi)容看最大值if(entry.size > maxSize){maxSize = entry.size;renderTime = entry.startTime;}}console.log('LCP', renderTime)
}).observe({type: 'largest-contentful-paint', buffered: true});
1.5 LongTask長(zhǎng)任務(wù)統(tǒng)計(jì)
LongTask
(長(zhǎng)任務(wù))是指在JavaScript
主線程上執(zhí)行時(shí)間超過50毫秒的任務(wù)。這些任務(wù)可能是復(fù)雜的計(jì)算、大量數(shù)據(jù)處理、DOM
操作或其他耗時(shí)的操作。
我們可以通過以下方式來獲取:
new PerformanceObserver((entryList) => {var list = entryList.getEntries();var entry = list[list.length-1];if(entry){console.log('LongTask',entry.startTime)}
}).observe({type: 'longtask', buffered: true});
一般我們?nèi)∽詈笠粋€(gè)就是長(zhǎng)任務(wù)的總耗時(shí)。這個(gè)值越低,性能越高。
二. 性能指標(biāo)計(jì)算測(cè)試
貼出案例代碼:
function test() {const entry = performance.getEntriesByType('navigation')[0]const {domComplete, secureConnectionStart, domInteractive, domContentLoadedEventStart, domainLookupEnd,domainLookupStart, connectEnd, connectStart, responseStart, requestStart, responseEnd, loadEventStart, domContentLoadedEventEnd, fetchStart, redirectEnd, redirectStart} = entry// redirectTimeconst redirectTime = redirectEnd - redirectStart// appcacheTimeconst appcacheTime = domainLookupStart - fetchStart// DNS解析時(shí)間const dnsTime = domainLookupEnd - domainLookupStart// TCP建立時(shí)間const tcpTime = connectEnd - connectStart// ssl 時(shí)間const sslTime = connectEnd - secureConnectionStart// requestTime 讀取頁面第一個(gè)字節(jié)的時(shí)間(請(qǐng)求時(shí)間)const requestTime = responseStart - requestStart// 返回響應(yīng)時(shí)間const responseTime = responseEnd - responseStart// domContentLoadedEventEnd - domContentLoadedEventStartconst domLoaded = domContentLoadedEventEnd - domContentLoadedEventStart// loadEventEnd - loadEventStartconst loadTime = loadEventStart - domContentLoadedEventEndconst dom1 = domInteractive - responseEnd// 解析dom樹耗時(shí)const dom2 = domComplete - domInteractiveconsole.log('********各個(gè)階段的消耗耗時(shí)********')console.log('redirectTime', redirectTime)console.log('appcacheTime', appcacheTime)console.log('dnsTime', dnsTime)console.log('tcpTime', tcpTime, '其中包括ssl時(shí)間', sslTime)console.log('TTFB(請(qǐng)求響應(yīng)耗時(shí))', requestTime)console.log('Trans(內(nèi)容傳輸耗時(shí))', responseTime)console.log('解析`DOM`階段', dom1)console.log('加載`DOM`階段', dom2, '其中包括(domContentLoadedEventEnd - domContentLoadedEventStart)', domLoaded)console.log('load事件耗時(shí)', loadTime)console.log('********校驗(yàn)時(shí)間差********')console.log('***********校驗(yàn) responseStart - fetchStart差值**************');console.log('responseStart - fetchStart', responseStart - fetchStart)console.log('appCache + dns+ tcp + requestTime 總和:', appcacheTime + dnsTime + tcpTime + requestTime)console.log('***********校驗(yàn) domInteractive - fetchStart差值**************');const tmp = appcacheTime + dnsTime + tcpTime + requestTime + responseTimeconst diff = domInteractive - fetchStartconsole.log('domInteractive - fetchStart', diff)console.log( 'appCache + dns+ tcp + request + response, 總和:', tmp, ', 空白時(shí)間', diff - tmp)console.log('空白時(shí)間(就是文檔解析和構(gòu)建DOM樹的過程),即DOM1(domInteractive - responseEnd)', dom1)// 算一下domCompelete - fetchStartconsole.log('domCompelete - fetchStart', domComplete - fetchStart)console.log('********FCP********')const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTimeconsole.log("LCP(通過performance.getEntriesByName('first-contentful-paint')計(jì)算出來的)", fcp)console.log('********LCP********')new PerformanceObserver((entryList) => {var maxSize = 0;var renderTime = 0;for (var entry of entryList.getEntries()) {// 渲染的內(nèi)容看最大值if (entry.size > maxSize) {maxSize = entry.size;renderTime = entry.startTime;}}console.log('LCP', renderTime)}).observe({ type: 'largest-contentful-paint', buffered: true });console.log('********LongTask********')new PerformanceObserver((entryList) => {var list = entryList.getEntries();var entry = list[list.length - 1];if (entry) {console.log('LongTask', entry.startTime)}}).observe({ type: 'longtask', buffered: true });
}
復(fù)制這段代碼到瀏覽器中,然后運(yùn)行test()
即可,結(jié)果如下:(FCP
打印錯(cuò)了)
代碼里主要打印了各個(gè)階段的耗時(shí)時(shí)長(zhǎng)。我們主要看下下半部分的校驗(yàn)部分。再把上面的圖搬過來對(duì)照著看:
2.1 衡量網(wǎng)絡(luò)請(qǐng)求響應(yīng)時(shí)間的指標(biāo)
從發(fā)起網(wǎng)絡(luò)請(qǐng)求(fetchStart
)到服務(wù)器開始響應(yīng)(responseStart
)的時(shí)間間隔。
responseStart - fetchStart
的差值為257毫秒。- 而這個(gè)差值由:
appCache + dns+ tcp + requestTime
4個(gè)階段連接而成,4個(gè)階段的時(shí)間總和為256.5毫秒,基本上接近。
2.2 衡量頁面加載速度的指標(biāo)
從發(fā)起網(wǎng)絡(luò)請(qǐng)求(fetchStart
)到DOM
解析完成(domInteractive
)的時(shí)間間隔
domInteractive - fetchStart
的差值為1328毫秒。- 而這個(gè)差值由:
appCache + dns+ tcp + request + response + 空白時(shí)間
部分組成(空白時(shí)間就是上圖的紅色框部分)。 - 我們可以看到,前5個(gè)區(qū)域的時(shí)間總和大概是:480毫秒。空白時(shí)間則848毫秒。
- 我們又計(jì)算了
domInteractive - responseEnd
的差值,實(shí)際上就是DOM1
,也就是加載DOM的時(shí)間,時(shí)間差為848毫秒,相吻合。
綜上所述:
- 頁面加載速度的指標(biāo)可以由:
DNS+TCP+Request+Response+DOM加載完畢耗時(shí)
的總和來決定。
另外我們還能看出來,LCP
的計(jì)算可以幾乎為:domInteractive - fetchStart
, 當(dāng)然你用PerformanceObserver
進(jìn)行監(jiān)聽也是可以的。
這里代表頁面加載速度,此時(shí)DOM
僅僅是解析完成,如果想看DOM
也加載完成的耗時(shí),看指標(biāo)domComplete - fetchStart
。
2.3 TTI(Time to Interactive)衡量頁面可交互性的指標(biāo)
TTI
:表示從頁面開始加載到用戶可以與頁面進(jìn)行交互的時(shí)間。它的計(jì)算方式如下:
FCP
時(shí)間為起始時(shí)間- 查找到指示有5s的靜默窗口時(shí)間(沒有長(zhǎng)任務(wù)并且不超過兩個(gè)正在執(zhí)行的
GET
請(qǐng)求)。 - 向后搜索靜默窗口前的最后一個(gè)長(zhǎng)任務(wù),如果沒有找到長(zhǎng)任務(wù),則在
FCP
上停止。 TTI
是在安靜窗口之前最后一個(gè)長(zhǎng)任務(wù)的結(jié)束時(shí)間(如果沒有找到長(zhǎng)任務(wù),則與FCP相同)
建議大家使用谷歌官方提供的:tti-polyfill
import ttiPolyfill from './path/to/tti-polyfill.js';ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {// Use `tti` value in some way.
});
當(dāng)然,也可以使用一種較為粗略的方式來計(jì)算:
- 首先我們理解一下
TTI
,是從頁面開始加載到用戶可以與頁面進(jìn)行交互的時(shí)間。 - 頁面開始加載,我們是不是可以看做
fetchStart
的時(shí)間。 - 頁面進(jìn)行交互的時(shí)間,那這個(gè)時(shí)候dom肯定是加載完畢了。我們按照非常極限的思路去想,這個(gè)是不是可以看做
dom
加載完畢的時(shí)間點(diǎn),即domComplete
。 - 那么
TTI ≈ domComplete - fetchStart
例如我用Lighthouse
計(jì)算出來的TTI
:
使用:domComplete - fetchStart
計(jì)算出來的值:
function getTTI(){const entry = performance.getEntriesByType('navigation')[0]const {domComplete,fetchStart} = entryconsole.log('TTI', domComplete - fetchStart)
}
getTTI()
結(jié)果如下:
2.4 TBT(Total Blocking Time)
TBT
就是衡量從FCP
時(shí)間點(diǎn)到TTI
這個(gè)時(shí)間點(diǎn)的時(shí)間區(qū)間內(nèi),所有超過50毫秒的長(zhǎng)任務(wù)的總耗時(shí)。(這個(gè)看下來難以通過編碼的方式來實(shí)現(xiàn)計(jì)算,也無法預(yù)估)
2.5 總結(jié)
- 我們?cè)跒轫撁孀鲂阅鼙O(jiān)控的時(shí)候,
LCP
和FCP
是我們的幾個(gè)重要關(guān)注對(duì)象。 LCP
可以通過PerformanceObserver
進(jìn)行檢測(cè)。FCP
可以通過performance.getEntriesByName('first-contentful-paint')[0].startTime
獲取。- 頁面性能的發(fā)部分?jǐn)?shù)據(jù)都可以從
performance.getEntriesByType('navigation')[0]
這里面獲取到。 - 如果你想衡量網(wǎng)絡(luò)請(qǐng)求響應(yīng)時(shí)間的指標(biāo):
responseStart - fetchStart
,代表從發(fā)起網(wǎng)絡(luò)請(qǐng)求(fetchStart
)到服務(wù)器開始響應(yīng)(responseStart
)的時(shí)間間隔。 - 如果你想衡量頁面加載速度的指標(biāo):
domInteractive - fetchStart
,代表從發(fā)起網(wǎng)絡(luò)請(qǐng)求(fetchStart
)到DOM
解析完成(domInteractive
)的時(shí)間間隔。 domComplete - fetchStart
這個(gè)差值基本上囊括了最核心的部分。包括了從開始獲取頁面資源到DOM
解析完成的整個(gè)過程,其中包括了網(wǎng)絡(luò)請(qǐng)求、資源加載、解析HTML
、構(gòu)建DOM
樹等操作。