瞭解使用 Navigation 和 Resource Timing API 來評估欄位載入效能的基本概念。
發布日期:2021 年 10 月 8 日
如果您曾在瀏覽器開發人員工具的網路面板中使用連線節流功能 (或 Chrome 中的 Lighthouse) 來評估載入效能,就會知道這些工具在效能調整上有多麼方便。你可以透過穩定一致的基準連線速度,快速評估效能最佳化設定的影響。唯一的問題在於這是合成測試,因此會產生研究室資料,而非現場資料。
合成測試有時不是「不良」,但不代表實際使用者載入網站的速度。這需要收集到現場資料,您可以透過 Navigation Timing 和 Resource Timing API 收集這些資料。
可協助您評估實地載入效能的 API
Navigation Timing 和 Resource Timing 是兩個相似的 API,兩者有許多重疊之處,但測量項目不同:
這些 API 會在效能輸入緩衝區中公開資料,您可以透過 JavaScript 在瀏覽器中存取。您可以透過多種方式查詢效能緩衝區,但常見的方式是使用 performance.getEntriesByType
:
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');
// Get Resource Timing entries:
performance.getEntriesByType('resource');
performance.getEntriesByType
會接受字串,說明您要從效能項目緩衝區擷取的項目類型。'navigation'
和 'resource'
分別擷取 Navigation Timing 和 Resource Timing API 的時間。
這些 API 提供的資訊量可能會讓您不知所措,但這些 API 是評估實地載入效能的關鍵,因為您可以從使用者造訪網站時收集這些時間資訊。
網路要求的生命週期和時間
收集並分析導覽和資源時間有點像考古學,在事後重建網路要求的一連串新生命。有時候,以視覺化方式呈現概念有助於理解概念,而瀏覽器的開發人員工具可協助您瞭解網路要求。
網路要求的生命週期各有不同,例如 DNS 查詢、連線建立、TLS 交涉和其他延遲來源。這些時間點會以 DOMHighResTimestamp
表示。視瀏覽器而定,計時精確度可能會精確到微秒,或四捨五入至毫秒。建議您詳細檢查這些階段,以及各階段與 Navigation Timeing 和資源 Timeming 之間的關聯。
DNS 查詢
使用者造訪網址時,系統會查詢網域名稱系統 (DNS),將網域轉譯為 IP 位址。這項程序可能會花費大量時間,您甚至可能需要在現場測量。Navigation 和 Resource 顯示兩個與 DNS 相關的時間:
domainLookupStart
是 DNS 查詢開始的時間。domainLookupEnd
是 DNS 查詢結束的時間。
如要計算 DNS 查詢總時間,請將結束指標減去開始指標:
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
連線協商
連線協商是影響載入效能的另一個因素,這是指連線至網路伺服器時產生的延遲時間。如果涉及 HTTPS,這個程序還會包含 TLS 協商時間。連結階段包含三種時間:
connectStart
是瀏覽器開始開啟連至網路伺服器的連線時。secureConnectionStart
標示用戶端開始 TLS 協商的時間。connectEnd
是指已建立與網路伺服器的連線。
測量總連線時間的方式與測量 DNS 查詢總時間的方式類似:您只需將結束時間減去開始時間即可。不過,如果未使用 HTTPS 或連線為持續性,則可能會出現額外的 secureConnectionStart
屬性,該屬性可能為 0
。如要評估 TLS 協商時間,請注意下列事項:
// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with
// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
// Awesome! Calculate it!
tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}
DNS 查詢和連線協商結束後,就會開始執行與擷取文件及其依附資源相關的時間。
要求與回應
載入效能會受到兩種因素的影響:
- 極端因素:延遲時間和頻寬等。除了選擇代管公司和 CDN 以外,由於使用者無論身在何處,他們都可以存取網路,因此這些公司大部分都無法掌控。
- 內建因素:伺服器和用戶端架構、資源大小,以及我們能否針對這些項目進行最佳化,這些因素都是由我們自行控管。
這兩種因素都會影響載入效能。與上述因素相關的時間點至關重要,因為這些因素說明下載資源所需的時間。導覽時機和資源時機都會使用下列指標說明載入效能:
fetchStart
標示瀏覽器開始擷取資源 (資源時間) 或導覽要求的文件 (導覽時間) 的時間點。這會在實際要求之前執行,也是瀏覽器檢查快取 (例如 HTTP 和Cache
例項) 的時間點。workerStart
會標示服務工作者的fetch
事件處理常式何時開始處理要求。如果沒有任何 Service Worker 控制目前頁面,這將是0
。requestStart
是瀏覽器發出要求的時間。responseStart
是回應的第一個位元組到達時。responseEnd
是收到回應的最後一個位元組。
這些時間點可讓您評估載入效能的多個面向,例如服務工作者內的快取查詢和下載時間:
// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;
// Service worker time plus response time
let workerTime = 0;
if (pageNav.workerStart > 0) {
workerTime = pageNav.responseEnd - pageNav.workerStart;
}
您也可以評估要求和回應延遲時間的其他層面:
const [pageNav] = performance.getEntriesByType('navigation');
// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;
// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;
// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;
其他可測量的項目
除了上述範例所述的用途之外,Navigation 時間和 Resource 時間也非常實用。以下是其他可能值得探討的相關時間點:
- 網頁重新導向:重新導向是導致延遲時間增加的常見原因,尤其是重新導向鏈結。延遲時間會透過多種方式增加,例如 HTTP 至 HTTPs 躍點,以及 302/未快取的 301 重新導向。
redirectStart
、redirectEnd
和redirectCount
的時間資訊有助於評估重新導向延遲時間。 - 卸載文件:在
unload
事件處理常式中執行程式碼的網頁中,瀏覽器必須先執行該程式碼,才能前往下一頁。unloadEventStart
和unloadEventEnd
會測量文件卸載情形。 - 文件處理:除非網站傳送的 HTML 酬載非常大,否則文件處理時間可能不會造成影響。如果您的情況符合這項描述,就可能想瞭解
domInteractive
、domContentLoadedEventStart
、domContentLoadedEventEnd
和domComplete
的顯示時間。
如何在程式碼中取得時間
目前顯示的所有範例都使用 performance.getEntriesByType
,但還有其他方式可以查詢效能項目緩衝區,例如 performance.getEntriesByName
和 performance.getEntries
。如果只需要進行簡單的分析,這些方法就很適合。不過,在其他情況下,這些方法可能會透過重複處理大量項目,甚至重複輪詢效能緩衝區來尋找新項目,導致主執行緒工作過度。
如要從效能輸入緩衝區收集項目,建議您使用 PerformanceObserver
。PerformanceObserver
會監聽成效項目,並在項目加入緩衝區時提供這些項目:
// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
// Get all resource entries collected so far:
const entries = observedEntries.getEntries();
// Iterate over entries:
for (let i = 0; i < entries.length; i++) {
// Do the work!
}
});
// Run the observer for Navigation Timing entries:
perfObserver.observe({
type: 'navigation',
buffered: true
});
// Run the observer for Resource Timing entries:
perfObserver.observe({
type: 'resource',
buffered: true
});
與直接存取效能輸入緩衝區相比,這種收集時間的做法可能會讓人覺得不太方便,但建議您將主執行緒與不提供重要且面向使用者的作業綁定。
如何撥打電話回家
收集完所有需要的時間後,您可以把資料傳送到端點,以便進一步分析。您可以使用 navigator.sendBeacon
或 fetch
(已設定 keepalive
選項) 來執行這項操作。這兩種方法都會以非阻斷方式將要求傳送至指定端點,並在必要時以可存活超過目前網頁工作階段的方式排入佇列:
// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
// Caution: If you have lots of performance entries, don't
// do this. This is an example for illustrative purposes.
const data = JSON.stringify(performance.getEntries());
// Send the data!
navigator.sendBeacon('/analytics', data);
}
在這個範例中,JSON 字串會傳送至 POST
酬載,您可以視需要對其進行解碼、處理,並儲存在應用程式後端。
結論
當您收集到指標後,就可以決定如何分析該欄位資料。分析現場資料時,請留意以下幾項通則,以確保得出的結論有意義:
- 避免使用平均值,因為這類指標無法代表任何使用者的使用體驗,而且可能有異常值。
- 依據百分位數。在以時間為準的效能指標資料集中,數值越低越好。也就是說,如果您將低百分位數列為優先,就只會關注最快的體驗。
- 優先使用值的長尾。將 75 百分位數以上的體驗列為優先,就能將重點放在最慢的體驗上。
本指南並非詳盡的導覽或資源計時資源,而是起點。以下提供幾項額外的實用資源:
- Navigation Timing 規格:
- 資源時間規格。
- ResourceTiming 實際應用。
- Navigation Timing API (MDN)
- Resource Timing API (MDN)
透過這些 API 和其提供的資料,您就能更深入瞭解實際使用者在載入效能方面的體驗,進而更有信心地在現場診斷及解決載入效能問題。