JS `User Timing API`:`performance.mark` 与 `performance.measure` 精确计时

各位观众老爷们,大家好!今天咱们聊聊前端性能优化中一个非常实用的小工具——User Timing API。这玩意儿就像一个精密的秒表,能帮你精确测量代码的执行时间,找到性能瓶颈,让你的网页跑得飞快!

咱们主要讲两个核心方法:performance.markperformance.measure。别害怕,它们的名字虽然听起来高大上,但用法非常简单,保证你一学就会!

Part 1: performance.mark – 标记时间点

performance.mark 的作用就像你在赛道上设置一个个的计时点。它会在浏览器的性能时间轴上记录一个时间戳,这个时间戳代表着你代码执行过程中某个关键时刻。

用法:

performance.mark('开始加载数据'); // 标记数据加载的开始时间

// 一堆加载数据的代码...

performance.mark('数据加载完成'); // 标记数据加载的结束时间

简单吧?performance.mark() 接收一个字符串参数,这个字符串就是你给这个时间点起的名字,方便你以后查找和使用。

更深入一点:

performance.mark() 会创建一个 PerformanceMark 对象,这个对象会被添加到浏览器的性能时间轴上。你可以通过 performance.getEntriesByType('mark') 来获取所有标记的时间点。

performance.mark('开始加载数据');
performance.mark('数据加载完成');

const marks = performance.getEntriesByType('mark');

console.log(marks); // 输出一个数组,包含 '开始加载数据' 和 '数据加载完成' 两个 PerformanceMark 对象

每个 PerformanceMark 对象都包含以下属性:

  • name: 你给这个时间点起的名字。
  • entryType: 固定为 "mark"。
  • startTime: 时间戳,表示这个时间点距离页面加载开始的时间(单位是毫秒)。
  • duration: 固定为 0,因为 mark 只是一个时间点,没有时间跨度。

举个栗子:

假设你在一个页面中加载一个图片,你想知道图片加载的时间。

<!DOCTYPE html>
<html>
<head>
  <title>Performance Mark Example</title>
</head>
<body>
  <img id="myImage" src="your_image.jpg">

  <script>
    const image = document.getElementById('myImage');

    image.onload = function() {
      performance.mark('imageLoaded');
      const marks = performance.getEntriesByType('mark');
      console.log(marks);
    };

    performance.mark('imageLoadStart');

    image.src = 'your_image.jpg'; // 确保重新设置 src 才能触发 onload 事件,如果是缓存图片,可能不会触发
  </script>
</body>
</html>

在这个例子中,我们在图片加载开始前和加载完成后分别使用 performance.mark() 标记了时间点。当图片加载完成后,我们就可以通过 performance.getEntriesByType('mark') 获取这两个时间点,并计算图片加载的时间。

Part 2: performance.measure – 计算时间间隔

有了 performance.mark,我们就可以标记一系列的时间点。接下来,performance.measure 就能派上用场了,它可以计算两个时间点之间的时间间隔,告诉你代码执行了多久。

用法:

performance.mark('开始加载数据');

// 一堆加载数据的代码...

performance.mark('数据加载完成');

performance.measure('数据加载耗时', '开始加载数据', '数据加载完成');

performance.measure() 接收三个参数:

  1. measureName: 你给这次测量起的名字,方便你以后查找和使用。
  2. startMarkName: 开始时间点的名字,必须是你之前用 performance.mark() 标记过的。
  3. endMarkName: 结束时间点的名字,同样必须是你之前用 performance.mark() 标记过的。

更深入一点:

performance.measure() 会创建一个 PerformanceMeasure 对象,这个对象也会被添加到浏览器的性能时间轴上。你可以通过 performance.getEntriesByType('measure') 来获取所有测量结果。

performance.mark('开始加载数据');
performance.mark('数据加载完成');

performance.measure('数据加载耗时', '开始加载数据', '数据加载完成');

const measures = performance.getEntriesByType('measure');

console.log(measures); // 输出一个数组,包含 '数据加载耗时' 这个 PerformanceMeasure 对象

每个 PerformanceMeasure 对象都包含以下属性:

  • name: 你给这次测量起的名字。
  • entryType: 固定为 "measure"。
  • startTime: 开始时间点的时间戳。
  • duration: 时间间隔,表示结束时间点和开始时间点之间的时间差(单位是毫秒)。

再举个栗子:

还是加载图片的例子,这次我们直接获取图片加载的时间。

<!DOCTYPE html>
<html>
<head>
  <title>Performance Measure Example</title>
</head>
<body>
  <img id="myImage" src="your_image.jpg">

  <script>
    const image = document.getElementById('myImage');

    image.onload = function() {
      performance.mark('imageLoaded');
      performance.measure('imageLoadTime', 'imageLoadStart', 'imageLoaded');
      const measures = performance.getEntriesByType('measure');
      console.log(measures);

      const imageLoadTime = measures[0].duration;
      console.log(`图片加载耗时:${imageLoadTime} 毫秒`);
    };

    performance.mark('imageLoadStart');
    image.src = 'your_image.jpg';
  </script>
</body>
</html>

在这个例子中,我们使用 performance.measure() 计算了图片加载的时间,并将结果输出到控制台。

Part 3: performance.clearMarksperformance.clearMeasures – 清理垃圾

随着你的代码越来越复杂,你可能会创建大量的 PerformanceMarkPerformanceMeasure 对象。为了避免内存泄漏,你可以使用 performance.clearMarks()performance.clearMeasures() 来清理这些对象。

用法:

performance.clearMarks('开始加载数据'); // 清理名为 '开始加载数据' 的 mark
performance.clearMarks(); // 清理所有 mark

performance.clearMeasures('数据加载耗时'); // 清理名为 '数据加载耗时' 的 measure
performance.clearMeasures(); // 清理所有 measure

performance.clearMarks()performance.clearMeasures() 都可以接收一个字符串参数,表示要清理的对象的名字。如果不传参数,则会清理所有对象。

Part 4: performance.getEntries – 获取所有性能条目

如果你想一次性获取所有类型的性能条目(包括 mark、measure、resource 等),可以使用 performance.getEntries() 方法。

用法:

const entries = performance.getEntries();

console.log(entries); // 输出一个数组,包含所有性能条目

performance.getEntries() 返回一个数组,包含所有类型的性能条目。你可以遍历这个数组,根据 entryType 属性来区分不同的条目类型。

Part 5: 应用场景与高级技巧

  • 测量函数执行时间:
function myFunction() {
  performance.mark('myFunctionStart');
  // 一些代码...
  performance.mark('myFunctionEnd');
  performance.measure('myFunctionExecutionTime', 'myFunctionStart', 'myFunctionEnd');
  const measures = performance.getEntriesByName('myFunctionExecutionTime')[0];
  console.log(`myFunction execution time: ${measures.duration} ms`);
}

myFunction();
  • 测量用户交互时间:
document.getElementById('myButton').addEventListener('click', function() {
  performance.mark('buttonClickStart');
  // 处理点击事件的代码...
  performance.mark('buttonClickEnd');
  performance.measure('buttonClickProcessingTime', 'buttonClickStart', 'buttonClickEnd');
  const measures = performance.getEntriesByName('buttonClickProcessingTime')[0];
  console.log(`Button click processing time: ${measures.duration} ms`);
});
  • 结合 requestAnimationFrame 进行更精确的测量: requestAnimationFrame 能确保你的代码在浏览器下一次重绘之前执行,这对于测量动画性能非常有用。
function animate() {
  performance.mark('animationFrameStart');

  // 执行动画相关的代码...

  performance.mark('animationFrameEnd');
  performance.measure('animationFrameDuration', 'animationFrameStart', 'animationFrameEnd');

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

// 然后,你可以定期(比如每秒)获取并分析 animationFrameDuration 的平均值和最大值。
setInterval(() => {
  const measures = performance.getEntriesByName('animationFrameDuration');
  let totalDuration = 0;
  for (const measure of measures) {
    totalDuration += measure.duration;
  }
  const averageDuration = totalDuration / measures.length;
  console.log(`Average animation frame duration: ${averageDuration.toFixed(2)} ms`);
  performance.clearMeasures('animationFrameDuration'); // 清除旧的测量数据
}, 1000);
  • 使用 performance.now() 进行更细粒度的测量: performance.now() 返回一个高精度的时间戳,可以用于测量非常短的时间间隔,比如几微秒。 虽然 performance.markperformance.measure 内部也是基于 performance.now(),但直接使用它可以让你更灵活地控制测量的开始和结束。
const startTime = performance.now();
// 一些非常耗时的代码...
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`代码执行时间:${duration.toFixed(4)} ms`);

Part 6: 兼容性与注意事项

  • User Timing API 的兼容性非常好,几乎所有现代浏览器都支持。
  • performance.markperformance.measure 的名字必须是唯一的,否则会覆盖之前的标记或测量。
  • 不要过度使用 User Timing API,过多的标记和测量会影响性能。只在需要精确测量的地方使用。
  • 在生产环境中,你应该移除或者禁用 User Timing API 的代码,因为它们会增加代码的体积,并且可能会暴露敏感信息。可以使用构建工具(如 Webpack 或 Parcel)来移除这些代码。
  • performance.now() 返回的时间戳是相对于 performance.timeOrigin 的,即页面加载开始的时间。
  • performance.getEntriesByName() 可以根据名字获取性能条目。

Part 7: 总结

User Timing API 是一个非常强大的工具,可以帮助你精确测量代码的执行时间,找到性能瓶颈,并进行优化。记住,优化是一个持续的过程,需要不断地测量、分析和改进。

方法/属性 作用 参数 返回值
performance.mark(name) 标记一个时间点 name: 时间点的名字 (字符串) undefined
performance.measure(name, startMark, endMark) 计算两个时间点之间的时间间隔 name: 测量结果的名字 (字符串), startMark: 开始时间点的名字 (字符串), endMark: 结束时间点的名字 (字符串) undefined
performance.clearMarks(name) 清除指定名字的 mark,不指定则清除所有 mark name: 要清除的 mark 的名字 (字符串, 可选) undefined
performance.clearMeasures(name) 清除指定名字的 measure,不指定则清除所有 measure name: 要清除的 measure 的名字 (字符串, 可选) undefined
performance.getEntriesByType(type) 获取指定类型的性能条目 type: 条目类型 (字符串, 例如: "mark", "measure", "resource") 一个数组,包含所有指定类型的性能条目
performance.getEntriesByName(name) 获取指定名字的性能条目 name: 条目的名字 (字符串) 一个数组,包含所有指定名字的性能条目
performance.getEntries() 获取所有性能条目 一个数组,包含所有性能条目
performance.now() 返回一个高精度的时间戳,表示当前时间距离页面加载开始的时间(单位是毫秒),精度可达微秒级别。 一个浮点数,表示当前时间距离页面加载开始的时间(单位是毫秒)

希望今天的讲解对大家有所帮助! 祝大家写出高性能的代码,告辞!

发表回复

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