JS `PerformanceEntry` 类型:自定义性能指标的收集与上报

各位朋友,大家好!我是你们今天的性能优化小能手。今天咱们不聊那些高大上的框架,也不啃那些难懂的算法,咱们就来聊聊一个浏览器自带的小工具,但能量却不小的东西——PerformanceEntry

咱们先来个热身:啥是 PerformanceEntry

想象一下,你开着一辆跑车(你的网站),想知道这辆车跑得怎么样,光看速度表可不行,还得看看油耗、发动机温度、轮胎磨损等等。PerformanceEntry 就相当于这辆跑车的各种传感器,它记录了你网站运行过程中的各种性能指标。

简单来说,PerformanceEntry 是一个接口,它表示一个性能度量事件。这些事件可以是浏览器内置的,比如页面加载时间、资源加载时间,也可以是你自己定义的,比如某个关键功能的执行时间。

为什么我们需要 PerformanceEntry

“知己知彼,百战不殆”,优化网站性能也是一样。你不了解问题出在哪里,怎么可能对症下药呢?PerformanceEntry 就像一个性能体检报告,告诉你网站的优点和缺点,让你能更有效地进行优化。

  • 精准定位性能瓶颈: 知道哪里慢了,才能针对性地优化。
  • 监控性能趋势: 长期监控性能指标,及时发现潜在问题。
  • 自定义性能指标: 衡量特定功能的性能,满足个性化需求。
  • 数据驱动优化: 用数据说话,让优化更有说服力。

PerformanceEntry 的家庭成员:各种类型的性能事件

PerformanceEntry 本身只是一个接口,它有很多不同的子类型,分别代表不同的性能事件。我们来认识一下几个常见的家庭成员:

类型(entryType 描述 常用属性
navigation 记录页面导航的性能数据,比如重定向、DNS 查询、TCP 连接、请求响应等等。 startTime, duration, redirectStart, redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, requestStart, responseStart, responseEnd, domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, loadEventStart, loadEventEnd
resource 记录资源(图片、CSS、JS 等)加载的性能数据,比如请求时间、响应时间、传输大小等等。 startTime, duration, name, initiatorType, transferSize, encodedBodySize, decodedBodySize, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, requestStart, responseStart, responseEnd
paint 记录首次渲染(First Paint, FP)和首次内容渲染(First Contentful Paint, FCP)的时间。 startTime, duration, name (可以是 "first-paint" 或 "first-contentful-paint")
longtask 记录执行时间超过 50 毫秒的任务,这些任务可能会阻塞主线程,影响用户体验。 startTime, duration, name (通常为空字符串)
measure 记录自定义的性能指标,比如某个函数的执行时间、某个操作的耗时等等。 startTime, duration, name
mark 记录代码中某个特定时间点的时间戳,用于后续计算性能指标。 startTime, name
event 记录事件的性能数据,例如鼠标点击、键盘输入等。 startTime, duration, name, interactionId

如何获取 PerformanceEntry

获取 PerformanceEntry 主要有两种方式:

  1. performance.getEntries(): 返回一个包含所有已记录的 PerformanceEntry 对象的数组。 你可以使用 performance.getEntriesByType(entryType) 或者 performance.getEntriesByName(name, entryType) 来过滤特定类型的或者特定名称的entry。
  2. PerformanceObserver: 创建一个观察者对象,它可以监听新的 PerformanceEntry 对象被创建。 这种方法可以实时的获取性能数据。

代码实战:获取页面加载时间

咱们先来个简单的例子,获取页面的完整加载时间(从开始导航到 load 事件触发):

window.addEventListener('load', () => {
  const navigationEntries = performance.getEntriesByType('navigation');
  if (navigationEntries.length > 0) {
    const navigationEntry = navigationEntries[0];
    const pageLoadTime = navigationEntry.loadEventEnd - navigationEntry.startTime;
    console.log(`页面加载时间: ${pageLoadTime} ms`);
  }
});

这段代码会在页面加载完成后执行,然后获取 navigation 类型的 PerformanceEntry,计算 loadEventEndstartTime 的差值,得到页面加载时间。

代码实战:监控资源加载时间

接下来,我们用 PerformanceObserver 来实时监控资源加载时间:

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType === 'resource') {
      console.log(`资源 ${entry.name} 加载时间: ${entry.duration} ms`);
    }
  });
});

observer.observe({ type: 'resource', buffered: true }); // buffered: true 表示获取观察器创建之前已经存在的资源

这段代码会创建一个 PerformanceObserver 对象,当有新的 resource 类型的 PerformanceEntry 被创建时,就会执行回调函数,打印资源加载时间。 buffered: true 确保我们也能获取到在观察器创建之前已经加载的资源。

自定义性能指标:markmeasure 的妙用

有时候,我们需要衡量一些特定的功能或操作的性能,这时就可以使用 markmeasure 来创建自定义的性能指标。

  • performance.mark(markName): 在代码中创建一个时间戳,用 markName 标记。
  • performance.measure(measureName, startMarkName, endMarkName): 计算 startMarkNameendMarkName 之间的时间差,用 measureName 标记。

代码实战:测量函数执行时间

假设我们要测量一个名为 myFunction 的函数执行时间:

function myFunction() {
  performance.mark('myFunctionStart');
  // 这里是函数的具体逻辑
  for (let i = 0; i < 1000000; i++) {
    // 模拟耗时操作
  }
  performance.mark('myFunctionEnd');
}

myFunction();

performance.measure('myFunctionDuration', 'myFunctionStart', 'myFunctionEnd');

const measureEntries = performance.getEntriesByName('myFunctionDuration', 'measure');
if (measureEntries.length > 0) {
  const measureEntry = measureEntries[0];
  console.log(`myFunction 执行时间: ${measureEntry.duration} ms`);
}

这段代码首先在 myFunction 函数的开始和结束位置分别创建了 mark,然后使用 measure 计算了这两个 mark 之间的时间差,最后获取 measure 类型的 PerformanceEntry,打印函数执行时间。

代码实战:结合 PerformanceObserver 实时监控自定义指标

我们还可以结合 PerformanceObserver 来实时监控自定义指标:

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType === 'measure') {
      console.log(`自定义指标 ${entry.name} 耗时: ${entry.duration} ms`);
    }
  });
});

observer.observe({ type: 'measure', buffered: true });

function doSomething() {
  performance.mark('doSomethingStart');
  // 一些耗时操作
  for (let i = 0; i < 500000; i++) {
    // 模拟耗时操作
  }
  performance.mark('doSomethingEnd');
  performance.measure('doSomethingDuration', 'doSomethingStart', 'doSomethingEnd');
}

doSomething();
doSomething(); // 多次执行,观察结果

清理战场:performance.clearMarks()performance.clearMeasures()

随着时间的推移,PerformanceEntry 会越来越多,可能会影响性能。我们可以使用 performance.clearMarks()performance.clearMeasures() 来清理 markmeasure 类型的 PerformanceEntry

performance.clearMarks('myFunctionStart'); // 清理名为 'myFunctionStart' 的 mark
performance.clearMeasures('myFunctionDuration'); // 清理名为 'myFunctionDuration' 的 measure

性能数据的上报和分析

光有数据还不够,还得把数据上报到服务器,进行分析和可视化。这样才能更好地了解网站的性能状况,并制定相应的优化策略。

常用的上报方式有:

  • fetch API: 使用 fetch API 将数据发送到服务器。
  • XMLHttpRequest: 使用 XMLHttpRequest 对象将数据发送到服务器。
  • navigator.sendBeacon(): 使用 navigator.sendBeacon() 方法在页面卸载时发送数据,不会阻塞页面卸载过程。

代码实战:使用 fetch 上报性能数据

function reportPerformanceData(data) {
  fetch('/api/performance', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  })
  .then(response => {
    if (!response.ok) {
      console.error('性能数据上报失败');
    }
  })
  .catch(error => {
    console.error('性能数据上报出错:', error);
  });
}

window.addEventListener('load', () => {
  const navigationEntries = performance.getEntriesByType('navigation');
  if (navigationEntries.length > 0) {
    const navigationEntry = navigationEntries[0];
    const pageLoadTime = navigationEntry.loadEventEnd - navigationEntry.startTime;
    const data = {
      pageLoadTime: pageLoadTime,
      url: window.location.href
    };
    reportPerformanceData(data);
  }
});

这段代码会在页面加载完成后,获取页面加载时间,然后使用 fetch API 将数据发送到 /api/performance 接口。

PerformanceEntry 的注意事项

  • 性能开销: 频繁地创建 markmeasure 可能会带来一定的性能开销,需要权衡。
  • 数据采样: 在生产环境中,可以采用数据采样的方式,只上报部分用户的性能数据,以减少服务器压力。
  • 隐私问题: 注意保护用户隐私,避免上报敏感信息。
  • 浏览器兼容性: PerformanceEntry 的兼容性较好,但仍需注意一些旧版本浏览器的兼容性问题。

总结:PerformanceEntry,你网站的性能管家

PerformanceEntry 是一个强大的性能监控工具,它可以帮助你了解网站的性能状况,定位性能瓶颈,并制定相应的优化策略。 通过灵活运用 PerformanceEntry 的各种功能,你可以打造一个更快、更流畅、更用户友好的网站!

今天就到这里,希望大家有所收获! 下次再见!

发表回复

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