使用 Navigation Timing 和 Resource Timing 在现场评估加载性能

学习使用 Navigation 和 Resource Timing API 在现场评估加载性能的基础知识。

如果您曾使用浏览器开发者工具的网络面板中的连接限制(或 Chrome 中的 Lighthouse)来评估加载性能,就会知道这些工具在进行性能优化方面的便利性如何。您可以采用一致且稳定的基准连接速度,快速衡量性能优化的影响。唯一的问题是,这是一种合成测试,产生的结果是实验室数据,而不是实测数据

合成测试本身并无糟糕,但不代表您的网站为真实用户加载的速度有多快。这需要字段数据,您可以通过 Navigation Timing API 和 Resource Timing API 收集。

帮助您评估现场加载性能的 API

Navigation Timing 和 Resource Timing 是两个具有显著重叠的类似 API,用于衡量两种不同的指标:

  • Navigation Timing 用于衡量 HTML 文档(即导航请求)的速度。
  • Resource Timing 衡量对与文档相关的资源(例如 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 Timing 和 Resource Timing 之间的关系。

DNS 查找

当用户访问网址时,系统会查询域名系统 (DNS),以将域名转换为 IP 地址。这个过程可能会耗费大量时间,甚至会用到一些时间,让您在现场进行测量。Navigation Timing 和 Resource Timing 提供了两种与 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 和 Resource Timing 都通过以下指标来描述加载性能:

  • fetchStart 标记浏览器何时开始为导航请求提取资源 (Resource Timing) 或文档 (Navigation Timing)。这发生在实际请求之前,并且是浏览器检查缓存(例如 HTTP 和 Cache 实例)的时间点。
  • workerStart 用于标记何时开始在 Service Worker 的 fetch 事件处理程序中处理请求。如果没有任何 Service Worker 正在控制当前页面,该值将为 0
  • requestStart 表示浏览器发出请求。
  • responseStart 表示到达响应的第一个字节。
  • responseEnd 表示到达响应的最后一个字节。

这些时间可让您衡量加载性能的多个方面,例如 Service Worker 中的缓存查询以及下载时间:

// 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 Timing 和 Resource Timing 的作用远不止于上面的示例。下面是其他一些具有相关时间的情况,可能值得一探:

  • 网页重定向:重定向会导致延迟时间被忽略,尤其是重定向链。通过多种方式增加延迟时间,例如 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.sendBeaconfetch 并设置 keepalive 选项。这两种方法都会以非阻塞方式向指定的端点发送请求,并且请求在排队时间比当前页面会话存在的时间更长(如果需要):

// 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 个百分位或更高的体验时,就意味着将注意力放在应该处理的位置:处理最慢的体验。

本指南并非关于 Navigation 或 Resource Timing 的详尽资源,而只是入门指南。以下是一些可能对您有帮助的其他资源:

借助这些 API 及其提供的数据,您将能够更好地了解真实用户对于加载性能的体验,从而更有信心诊断和解决现场加载性能问题。