JS `PerformanceEntry` `resource timing` 与 `navigation timing` 细节分析

大家好!我是你们今天的性能优化导师,咱们今天来聊聊JavaScript中那些藏得很深的性能秘密——PerformanceEntryresource timingnavigation timing。别怕,听名字高大上,其实理解起来就像解一道有趣的数学题。

欢迎来到性能优化的“时间旅行”

在网页性能优化中,时间就是金钱,更准确地说,是用户体验。PerformanceEntry API 就像一个时间旅行的记录仪,记录着网页加载和资源获取过程中发生的各种事件。而 resource timingnavigation timing 则是这个记录仪上的两份重要报告,详细记录了资源加载和页面导航过程中的时间数据。

PerformanceEntry:一切性能数据的基石

PerformanceEntry 是一个接口,代表一个单独的性能度量事件。它是一个抽象类,实际使用中,我们主要接触的是它的子类,比如 PerformanceResourceTimingPerformanceNavigationTiming

PerformanceEntry 都有哪些属性?

属性名 类型 描述
name string 性能度量事件的名称。例如,如果是 PerformanceResourceTiming,它通常是资源的 URL。如果是 PerformanceNavigationTiming,它通常是文档的 URL。
entryType string 性能度量事件的类型。例如,"resource""navigation"
startTime DOMHighResTimeStamp 性能度量事件开始的时间戳(相对于 performance 起始时间)。
duration DOMHighResTimeStamp 性能度量事件的持续时间(毫秒)。

如何获取 PerformanceEntry?

主要有两种方式:

  1. performance.getEntries(): 获取所有类型的 PerformanceEntry 数组。
  2. performance.getEntriesByType(entryType): 获取特定类型的 PerformanceEntry 数组,比如 "resource""navigation"
  3. 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?

  1. 获取 PerformanceResourceTiming 对象: 使用 performance.getEntriesByType("resource")performance.getEntriesByName(name, "resource") 获取。
  2. 分析时间信息: 通过访问对象的属性,获取各个阶段的时间戳,并计算时间差。

代码示例:

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,说明资源是从缓存中加载的。如果 transferSizedecodedBodySize 都很大,说明资源没有被缓存,需要检查缓存配置。

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?

  1. 获取 PerformanceNavigationTiming 对象: 使用 performance.getEntriesByType("navigation")[0] 获取。通常情况下,一个页面只有一个 navigation entry。
  2. 分析时间信息: 通过访问对象的属性,获取各个阶段的时间戳,并计算时间差。

代码示例:

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 提供的时间信息,可以优化首屏渲染,提高用户体验。

总结:时间旅行的意义

PerformanceEntryresource timingnavigation 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,并在你的性能优化工作中游刃有余!祝你优化愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注