大家好!我是你们今天的性能优化导师,咱们今天来聊聊JavaScript中那些藏得很深的性能秘密——PerformanceEntry
、resource timing
和 navigation timing
。别怕,听名字高大上,其实理解起来就像解一道有趣的数学题。
欢迎来到性能优化的“时间旅行”
在网页性能优化中,时间就是金钱,更准确地说,是用户体验。PerformanceEntry
API 就像一个时间旅行的记录仪,记录着网页加载和资源获取过程中发生的各种事件。而 resource timing
和 navigation timing
则是这个记录仪上的两份重要报告,详细记录了资源加载和页面导航过程中的时间数据。
PerformanceEntry:一切性能数据的基石
PerformanceEntry
是一个接口,代表一个单独的性能度量事件。它是一个抽象类,实际使用中,我们主要接触的是它的子类,比如 PerformanceResourceTiming
和 PerformanceNavigationTiming
。
PerformanceEntry 都有哪些属性?
属性名 | 类型 | 描述 |
---|---|---|
name |
string |
性能度量事件的名称。例如,如果是 PerformanceResourceTiming ,它通常是资源的 URL。如果是 PerformanceNavigationTiming ,它通常是文档的 URL。 |
entryType |
string |
性能度量事件的类型。例如,"resource" 或 "navigation" 。 |
startTime |
DOMHighResTimeStamp |
性能度量事件开始的时间戳(相对于 performance 起始时间)。 |
duration |
DOMHighResTimeStamp |
性能度量事件的持续时间(毫秒)。 |
如何获取 PerformanceEntry?
主要有两种方式:
performance.getEntries()
: 获取所有类型的PerformanceEntry
数组。performance.getEntriesByType(entryType)
: 获取特定类型的PerformanceEntry
数组,比如"resource"
或"navigation"
。performance.getEntriesByName(name, entryType)
: 获取指定名称和类型的PerformanceEntry
数组。
代码示例:
// 获取所有 PerformanceEntry
const entries = performance.getEntries();
console.log("所有 PerformanceEntry:", entries);
// 获取所有 resource 类型的 PerformanceEntry
const resourceEntries = performance.getEntriesByType("resource");
console.log("Resource PerformanceEntry:", resourceEntries);
// 获取指定名称的 resource 类型的 PerformanceEntry (例如,名为 "my-image.png" 的资源)
const imageEntries = performance.getEntriesByName("my-image.png", "resource");
console.log("Image PerformanceEntry:", imageEntries);
Resource Timing API:资源加载的“X光片”
PerformanceResourceTiming
接口提供了关于浏览器加载资源的详细时间信息。它继承自 PerformanceEntry
,并添加了更多与资源加载相关的属性。你可以把它想象成一张资源加载的“X光片”,能让你清楚地看到资源加载的每一个阶段。
PerformanceResourceTiming 都有哪些关键属性?
属性名 | 类型 | 描述 |
---|---|---|
initiatorType |
string |
启动资源请求的元素类型。例如,"img" , "script" , "link" , "xmlhttprequest" , "fetch" 等。 |
nextHopProtocol |
string |
用于获取资源的协议。例如,"h2" , "http/1.1" , "quic" 等。 |
redirectStart |
DOMHighResTimeStamp |
重定向开始的时间戳。如果没有重定向,则为 0。 |
redirectEnd |
DOMHighResTimeStamp |
重定向结束的时间戳。如果没有重定向,则为 0。 |
fetchStart |
DOMHighResTimeStamp |
浏览器准备好从服务器获取资源的时间戳,无论从缓存还是网络。 |
domainLookupStart |
DOMHighResTimeStamp |
DNS 查询开始的时间戳。 |
domainLookupEnd |
DOMHighResTimeStamp |
DNS 查询结束的时间戳。 |
connectStart |
DOMHighResTimeStamp |
TCP 连接开始的时间戳。 |
connectEnd |
DOMHighResTimeStamp |
TCP 连接结束的时间戳。 |
secureConnectionStart |
DOMHighResTimeStamp |
SSL/TLS 握手开始的时间戳。如果未使用安全连接,则为 0。 |
requestStart |
DOMHighResTimeStamp |
浏览器向服务器发出 HTTP 请求的时间戳。 |
responseStart |
DOMHighResTimeStamp |
浏览器从服务器接收到(或从本地缓存读取)第一个字节的时间戳。 |
responseEnd |
DOMHighResTimeStamp |
浏览器从服务器接收到(或从本地缓存读取)最后一个字节的时间戳。 |
transferSize |
number |
资源传输的大小(字节)。 |
encodedBodySize |
number |
响应体的编码大小(字节)。 |
decodedBodySize |
number |
响应体的解码大小(字节)。 |
serverTiming |
readonly ServerTiming[] |
服务器返回的 Server-Timing 头部信息。 |
如何使用 Resource Timing API?
- 获取 PerformanceResourceTiming 对象: 使用
performance.getEntriesByType("resource")
或performance.getEntriesByName(name, "resource")
获取。 - 分析时间信息: 通过访问对象的属性,获取各个阶段的时间戳,并计算时间差。
代码示例:
const resourceEntries = performance.getEntriesByType("resource");
resourceEntries.forEach(entry => {
const {
name,
initiatorType,
transferSize,
duration,
domainLookupStart,
domainLookupEnd,
connectStart,
connectEnd,
requestStart,
responseStart,
responseEnd
} = entry;
const dnsLookupTime = domainLookupEnd - domainLookupStart;
const connectTime = connectEnd - connectStart;
const requestTime = responseStart - requestStart;
const responseTime = responseEnd - responseStart;
console.log(`资源: ${name}`);
console.log(`发起者: ${initiatorType}`);
console.log(`传输大小: ${transferSize} 字节`);
console.log(`总耗时: ${duration} 毫秒`);
console.log(`DNS 查询耗时: ${dnsLookupTime} 毫秒`);
console.log(`连接耗时: ${connectTime} 毫秒`);
console.log(`请求耗时: ${requestTime} 毫秒`);
console.log(`响应耗时: ${responseTime} 毫秒`);
console.log("---");
});
Resource Timing API 的应用场景:
- 分析资源加载瓶颈: 找出耗时最长的资源,并针对性地进行优化。例如,如果 DNS 查询耗时很长,可以考虑使用 CDN 或 DNS 预解析。
- 监控第三方资源性能: 如果你的网站使用了第三方资源,可以使用 Resource Timing API 监控它们的性能,及时发现问题。
- 优化资源加载策略: 通过分析资源加载的时间信息,可以优化资源的加载顺序,例如,优先加载关键资源。
- 排查缓存问题: 如果资源的
transferSize
为 0,但decodedBodySize
不为 0,说明资源是从缓存中加载的。如果transferSize
和decodedBodySize
都很大,说明资源没有被缓存,需要检查缓存配置。
Navigation Timing API:页面导航的“时间轴”
PerformanceNavigationTiming
接口提供了关于文档导航的详细时间信息。它也继承自 PerformanceEntry
,并添加了与页面导航相关的属性。你可以把它想象成页面导航的“时间轴”,能让你了解用户从发起导航到页面加载完成的整个过程。
PerformanceNavigationTiming 都有哪些关键属性?
属性名 | 类型 | 描述 |
---|---|---|
navigationStart |
DOMHighResTimeStamp |
导航开始的时间戳。当用户开始导航时,无论通过链接点击、书签、表单提交还是脚本操作,都会记录这个时间。 |
unloadEventStart |
DOMHighResTimeStamp |
前一个文档的 unload 事件处理程序开始运行的时间戳。如果前一个文档没有 unload 事件处理程序,或者前一个文档和当前文档来自同一个源,则为 0。 |
unloadEventEnd |
DOMHighResTimeStamp |
前一个文档的 unload 事件处理程序运行结束的时间戳。如果前一个文档没有 unload 事件处理程序,或者前一个文档和当前文档来自同一个源,则为 0。 |
redirectStart |
DOMHighResTimeStamp |
重定向开始的时间戳。如果没有重定向,则为 0。 |
redirectEnd |
DOMHighResTimeStamp |
重定向结束的时间戳。如果没有重定向,则为 0。 |
fetchStart |
DOMHighResTimeStamp |
浏览器准备好从服务器获取文档的时间戳,无论从缓存还是网络。 |
domainLookupStart |
DOMHighResTimeStamp |
DNS 查询开始的时间戳。 |
domainLookupEnd |
DOMHighResTimeStamp |
DNS 查询结束的时间戳。 |
connectStart |
DOMHighResTimeStamp |
TCP 连接开始的时间戳。 |
connectEnd |
DOMHighResTimeStamp |
TCP 连接结束的时间戳。 |
secureConnectionStart |
DOMHighResTimeStamp |
SSL/TLS 握手开始的时间戳。如果未使用安全连接,则为 0。 |
requestStart |
DOMHighResTimeStamp |
浏览器向服务器发出 HTTP 请求的时间戳。 |
responseStart |
DOMHighResTimeStamp |
浏览器从服务器接收到(或从本地缓存读取)第一个字节的时间戳。 |
responseEnd |
DOMHighResTimeStamp |
浏览器从服务器接收到(或从本地缓存读取)最后一个字节的时间戳。 |
domLoading |
DOMHighResTimeStamp |
浏览器开始解析 HTML 文档的时间戳。 |
domInteractive |
DOMHighResTimeStamp |
浏览器完成解析 HTML 文档,并开始构建 DOM 树的时间戳。此时,DOMContentLoaded 事件将被触发。 |
domContentLoadedEventStart |
DOMHighResTimeStamp |
DOMContentLoaded 事件处理程序开始执行的时间戳。 |
domContentLoadedEventEnd |
DOMHighResTimeStamp |
DOMContentLoaded 事件处理程序执行结束的时间戳。 |
domComplete |
DOMHighResTimeStamp |
浏览器完成解析 HTML 文档,并完成所有资源加载的时间戳。此时,load 事件将被触发。 |
loadEventStart |
DOMHighResTimeStamp |
load 事件处理程序开始执行的时间戳。 |
loadEventEnd |
DOMHighResTimeStamp |
load 事件处理程序执行结束的时间戳。 |
type |
NavigationType |
导航的类型。可能的值包括 "navigate" (通过点击链接、书签等),"reload" (通过刷新页面),"back_forward" (通过浏览器的前进/后退按钮),"prerender" (预渲染页面)。 |
redirectCount |
number |
重定向的次数。 |
如何使用 Navigation Timing API?
- 获取 PerformanceNavigationTiming 对象: 使用
performance.getEntriesByType("navigation")[0]
获取。通常情况下,一个页面只有一个 navigation entry。 - 分析时间信息: 通过访问对象的属性,获取各个阶段的时间戳,并计算时间差。
代码示例:
const navigationEntry = performance.getEntriesByType("navigation")[0];
if (navigationEntry) {
const {
navigationStart,
unloadEventStart,
unloadEventEnd,
redirectStart,
redirectEnd,
fetchStart,
domainLookupStart,
domainLookupEnd,
connectStart,
connectEnd,
secureConnectionStart,
requestStart,
responseStart,
responseEnd,
domLoading,
domInteractive,
domContentLoadedEventStart,
domContentLoadedEventEnd,
domComplete,
loadEventStart,
loadEventEnd,
type,
redirectCount
} = navigationEntry;
const redirectTime = redirectEnd - redirectStart;
const dnsLookupTime = domainLookupEnd - domainLookupStart;
const connectTime = connectEnd - connectStart;
const requestTime = responseStart - requestStart;
const responseTime = responseEnd - responseStart;
const domInteractiveTime = domInteractive - domLoading;
const domContentLoadedTime = domContentLoadedEventEnd - domContentLoadedEventStart;
const domCompleteTime = domComplete - domLoading;
const loadEventTime = loadEventEnd - loadEventStart;
const totalLoadTime = loadEventEnd - navigationStart;
console.log(`导航类型: ${type}`);
console.log(`重定向次数: ${redirectCount}`);
console.log(`重定向耗时: ${redirectTime} 毫秒`);
console.log(`DNS 查询耗时: ${dnsLookupTime} 毫秒`);
console.log(`连接耗时: ${connectTime} 毫秒`);
console.log(`请求耗时: ${requestTime} 毫秒`);
console.log(`响应耗时: ${responseTime} 毫秒`);
console.log(`DOM Interactive 耗时: ${domInteractiveTime} 毫秒`);
console.log(`DOMContentLoaded 耗时: ${domContentLoadedTime} 毫秒`);
console.log(`DOM Complete 耗时: ${domCompleteTime} 毫秒`);
console.log(`Load Event 耗时: ${loadEventTime} 毫秒`);
console.log(`总加载时间: ${totalLoadTime} 毫秒`);
}
Navigation Timing API 的应用场景:
- 分析页面加载瓶颈: 找出页面加载过程中耗时最长的阶段,并针对性地进行优化。例如,如果 DNS 查询耗时很长,可以考虑使用 CDN 或 DNS 预解析。如果 DOM 解析耗时很长,可以优化 HTML 结构。
- 监控关键性能指标: 可以使用 Navigation Timing API 监控一些关键性能指标,例如,First Contentful Paint (FCP)、Largest Contentful Paint (LCP)、Time to Interactive (TTI) 等。
- 对比不同导航类型的性能: 可以对比不同导航类型(例如,
"navigate"
,"reload"
,"back_forward"
) 的性能,找出性能差异的原因。 - 优化首屏渲染: 通过分析 Navigation Timing API 提供的时间信息,可以优化首屏渲染,提高用户体验。
总结:时间旅行的意义
PerformanceEntry
、resource timing
和 navigation timing
API 就像一把钥匙,打开了网页性能优化的宝藏。通过分析这些 API 提供的时间信息,我们可以深入了解网页加载和资源获取的每一个细节,找出性能瓶颈,并针对性地进行优化。
记住,性能优化是一个持续的过程,需要不断地监控、分析和改进。希望今天的“时间旅行”能帮助你更好地理解这些 API,并在你的性能优化之旅中取得更大的成功!
进阶:PerformanceObserver
如果你想实时监听性能事件,而不是在页面加载完成后一次性获取所有数据,可以使用 PerformanceObserver
API。
如何使用 PerformanceObserver?
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log("新的 PerformanceEntry:", entry);
// 在这里处理 entry,例如,发送到服务器进行分析
});
});
observer.observe({ type: "resource", buffered: true }); // 监听 resource 类型的 PerformanceEntry
observer.observe({ type: "navigation", buffered: true }); // 监听 navigation 类型的 PerformanceEntry
buffered: true
表示获取在观察器创建之前已经发生的性能事件。
PerformanceObserver 的应用场景:
- 实时监控性能: 可以使用 PerformanceObserver 实时监控性能事件,及时发现问题。
- 自动化性能测试: 可以使用 PerformanceObserver 编写自动化性能测试,定期检查网站的性能。
- 收集用户性能数据: 可以使用 PerformanceObserver 收集用户的性能数据,用于分析和优化网站的性能。
希望这些更深入的讲解能帮助你更好地掌握这些 API,并在你的性能优化工作中游刃有余!祝你优化愉快!