使用 Navigation 時間和資源時間評估欄位中的載入效能

瞭解使用 Navigation 和資源 Timing API 評估欄位載入效能的基本概念。

如果您已在瀏覽器開發人員工具中的網路面板使用連線節流功能 (或 Chrome 中的 Lighthouse) 評估載入效能,就表示這些工具在調整效能方面有多方便。您可以透過一致且穩定的基準連線速度,快速評估效能最佳化的影響。唯一的問題是綜合測試,這會產生研究室資料,而非現場資料

綜合測試並非在本質上就「不良」,但該結果無法反映網站實際載入使用者的速度。需要使用欄位資料,您可以從 Navigation Timing 和 Resource Timing API 收集這些資料。

協助您評估實際載入效能的 API

Navigation 時間與資源時間是兩個相似的 API,主要重疊,分別評估兩種不同的項目:

  • Navigation Timing 會測量 HTML 文件 (亦即導覽要求) 的速度。
  • 資源時間會評估 CSS、JavaScript、圖片等文件相依資源的要求速度。

這些 API 會在效能項目緩衝區中公開資料,您可以透過 JavaScript 在瀏覽器中存取該緩衝區。查詢效能緩衝區的方法有很多種,但常見的方法是使用 performance.getEntriesByType

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType 接受字串,說明要從效能項目緩衝區擷取的項目類型。'navigation''resource' 會分別擷取 Navigation Timing API 和 Resource Timing API 的時間。

這類 API 提供的資訊量可能會令人頭痛,卻是評估欄位載入效能的關鍵,因為您可以在使用者造訪網站時收集這些時間資訊。

網路請求的生命週期和時間

收集和分析導覽和資源時間的方式有點像考古學,也就是在事後重建網路要求的生命週期。有時透過視覺化的方式瞭解概念,以及擔心網路請求方面的問題,瀏覽器的開發人員工具可以提供協助。

Chrome 開發人員工具中顯示的網路時間圖表。所描述的時間分別適用於要求排入佇列、連線協商、要求本身,以及彩色長條中的回應。
Chrome 開發人員工具的網路面板中的網路要求示意圖

網路要求的生命週期分為不同的階段,例如 DNS 查詢、連線建立、TLS 交涉等。這些時間會以 DOMHighResTimestamp 表示。視瀏覽器而定,時間點精細程度可能會降至微秒,或四捨五入至毫秒。以下將詳細說明這些階段,以及各階段與 Navigation 時間與資源時間之間的關係。

DNS 查詢

使用者前往網址時,系統會查詢網域名稱系統 (DNS),將網域轉譯為 IP 位址。這個程序可能需要大量時間,有時您也需要在欄位中進行測量。Navigation 時間與資源時間會顯示兩個 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,他們多半不受我們的控制,因為使用者可以隨時隨地存取網路。
  • 內建因素:這些因素包括伺服器和用戶端架構、資源大小,以及我們針對這些因素進行最佳化的能力,這些因素都由我們掌控。

這兩種因素都會影響載入效能。與這些因素相關的時間很重要,因為這類時間可說明資源下載所需時間。Navigation Timing 和資源時間都會透過下列指標描述載入效能:

  • 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 時間與資源時間也對其他用途有幫助。以下是其他可能符合需求的時程,供您參考:

  • 網頁重新導向:重新導向是導致延遲增加的原因,特別是重新導向鏈結。透過多種方式延長延遲時間,例如 HTTP 對 HTTP 躍點以及 302/非快取 301 重新導向。redirectStartredirectEndredirectCount 時間有助於評估重新導向延遲時間。
  • 文件卸載:在執行 unload 事件處理常式中的程式碼時,瀏覽器必須先執行該程式碼,才能前往下一頁。unloadEventStartunloadEventEnd 會測量文件卸載。
  • 文件處理:除非網站傳送大量 HTML 酬載,否則文件處理時間可能不同。如果情況符合您的情況,請參閱 domInteractivedomContentLoadedEventStartdomContentLoadedEventEnddomComplete 的時間。

在應用程式程式碼中取得時間

到目前為止,所有範例都使用 performance.getEntriesByType,但還有其他查詢效能項目緩衝區的方法,例如 performance.getEntriesByNameperformance.getEntries。這些方法僅適用光分析時。在其他情況下,這類模型會疊代大量項目,甚至反覆輪詢效能緩衝區來尋找新項目,造成主執行緒工作量過大。

如要從效能項目緩衝區收集項目,建議您使用 PerformanceObserverPerformanceObserver 會監聽效能項目,並在將其新增至緩衝區時提供:

// 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 或已設定 keepalive 選項的 fetch。這兩種方法都會以非阻塞的方式,將要求傳送至指定的端點,而在必要時,要求就會排入超越目前網頁工作階段的佇列中:

// 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()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}

在這個範例中,JSON 字串會到達 POST 酬載,您可以視需要在應用程式後端進行解碼和處理/儲存。

設定程序即將完成

收集指標後,下一步就是決定如何分析該欄位資料。分析欄位資料時,請遵循以下幾項一般規則,確保您得出的結論是有意義的:

  • 避免使用平均值,因為該平均值無法代表任何使用者的體驗,而且可能會受離群值的偏差。
  • 仰賴百分位數。在以時間為主的成效指標中,值越低越好。也就是說,當您優先使用較低的百分位數時,就只會注意速度最快的使用者體驗。
  • 優先使用長尾的值。當您要優先處理第 75 個百分位數以上的體驗,

本指南並非針對導覽或資源時間的完整說明,而是起始點。以下提供幾項額外的實用資源:

使用這些 API 和所提供的資料,您就能更充分地瞭解實際使用者對於載入效能的體驗,進而提高診斷及解決實際載入效能問題。