识别运行繁重的 JavaScript

Web 功能: 长动画帧性能条目

运行繁重的 JavaScript 可能会对网页加载性能和互动度产生不利影响。现代 Web 应用比以往任何时候都更加依赖来自多个来源的 JavaScript。这些代码包括应用代码本身(及其所依赖的框架代码),以及用于添加聊天 widget 和视频播放器等功能的第三方脚本。后台分析和营销脚本也是常见的拖慢速度因素,而且很容易被忽视。

识别网页无响应的根本原因可能很棘手,需要具备一定的专业知识才能运行网页性能跟踪或分析,以及如何解读结果。此外,实测数据通常与实验室数据大不相同,后者仅复制了真实用户场景的一小部分。这可能会导致难以找出性能不佳的根本原因,尤其是对于互动而言。

长动画帧 API 是一种轻量级 API,可用于识别现场中运行繁重的 JavaScript。运行繁重的脚本可以是单个长时间运行的脚本,也可以是在页面生命周期内多次运行的脚本。

如何实现

长动画帧通过 PerformanceObserver 接口进行监控。当动画帧的渲染时间超过 50 毫秒时,它会发出 long-animation-frame 条目。该条目包含有关长动画帧的信息,包括帧的时长以及在帧期间执行的脚本。

long-animation-frame 条目包含一个 scripts 属性,该属性是一个 PerformanceScript 对象数组。每个 PerformanceScript 对象都包含在长动画帧期间执行的脚本的相关信息,包括脚本的 sourceURL 和 duration。

示例:识别导致长动画帧的运行时间最长的脚本

// Accumulate all script entries across the page lifecycle so no
// data is lost between observer callbacks.
const allScripts = [];

const observer = new PerformanceObserver(list => {
  // Collect all script entries across frames to find the biggest offenders.
  allScripts.push(...list.getEntries().flatMap(entry => entry.scripts));

  // Group by sourceURL so you can identify which scripts contribute
  // the most total time, even if each individual invocation is short.
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource = scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));

  // Only include scripts above a certain threshold to reduce noise.
  const heavyScripts = processedScripts.filter(script => {
    return script.totalDuration > 100;
  });

  // Sort by total duration so the worst offenders appear first,
  // making it easier to prioritize optimization efforts.
  heavyScripts.sort((a, b) => b.totalDuration - a.totalDuration);

  // Log to the console for local debugging. In production, replace
  // this with a call to send the data to your analytics service.
  console.table(heavyScripts);
});

// Use buffered: true to capture any long frames that occurred before
// this observer was registered.
observer.observe({type: 'long-animation-frame', buffered: true});

最佳做法

  • 建议优先使用 Long Animation Frames API,而不是 JS Self-Profiling API 等替代方案,因为后者的运行时开销更高。
  • 务必总结关键信息,因为 Long Animation Frames API 包含大量细节。
  • 务必将所需信息发送到生产环境中的分析服务。

浏览器支持和后备策略

不支持 Long Animation Frames API 的浏览器会忽略该 API,因此可以安全地使用该 API,而无需后备方案。在大多数情况下,它发现的性能提升机会也适用于其他浏览器。