学习使用 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 提供的信息量可能非常多,但它们是衡量现场加载性能的关键,因为您可以在用户访问您的网站时从他们那里收集这些时间。
网络请求的生命周期和时间
收集和分析导航和资源耗时有点像考古学,因为您要在事后重建网络请求短暂的生命周期。有时,直观了解概念会有所帮助,在涉及网络请求的情况下,浏览器的开发者工具可以有所帮助。
网络请求的生命周期具有不同的阶段,例如 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 重定向。
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
选项。这两种方法都会以非阻塞方式向指定的端点发送请求,并且请求在排队时间比当前页面会话存在的时间更长(如果需要):
// 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 的详尽资源,而只是入门指南。以下是一些可能对您有帮助的其他资源:
- Navigation Timing 规范。
- Resource Timing 规范。
- ResourceTiming 实际运用。
- Navigation Timing API (MDN)
- Resource Timing API (MDN)
借助这些 API 及其提供的数据,您将能够更好地了解真实用户对于加载性能的体验,从而更有信心诊断和解决现场加载性能问题。