自定义指标

拥有以用户为中心的指标非常有用,您可以普遍地在任何给定网站上衡量这些指标。通过这些指标,您可以:

  • 了解真实用户在整个网络中的体验。
  • 将您的网站与竞争对手的网站进行比较。
  • 在分析工具中跟踪实用且可据以采取行动的数据,而无需编写自定义代码。

通用指标可提供良好的基准,但在许多情况下,您需要衡量更多指标,才能全面了解特定网站的体验。

借助自定义指标,您可以衡量可能仅适用于您网站的网站体验方面,例如:

  • 单页应用 (SPA) 从一个“页面”转换到另一个“页面”所需的时间。
  • 网页为已登录用户显示从数据库中提取的数据所用的时间。
  • 服务器端渲染 (SSR) 应用水合所需的时间。
  • 回访者加载的资源的缓存命中率。
  • 游戏中点击或键盘事件的事件延迟时间。

用于衡量自定义指标的 API

过去,Web 开发者没有多少低级 API 来衡量性能,因此他们不得不求助于黑客技术来衡量网站的性能是否良好。

例如,可以通过运行 requestAnimationFrame 循环并计算每帧之间的增量,来确定主线程是否因长时间运行的 JavaScript 任务而被阻塞。如果增量明显长于显示屏的帧速率,您可以将其报告为长任务。不过,我们不建议使用此类黑客技术,因为它们实际上会影响性能(例如,耗尽电池电量)。

有效衡量性能的第一条规则是确保性能衡量技术本身不会导致性能问题。因此,对于您在网站上衡量的任何自定义指标,最好尽可能使用以下 API 之一。

Performance Observer API

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 11.

Source

Performance Observer API 是一种机制,用于收集和显示本页讨论的所有其他性能 API 的数据。了解这一点对于获得优质数据至关重要。

您可以使用 PerformanceObserver 被动订阅与性能相关的事件。这样一来,API 回调会在空闲时段触发,这意味着它们通常不会干扰网页性能。

如需创建 PerformanceObserver,请向其传递一个回调,以便在每次调度新的性能条目时运行该回调。然后,您可以使用 observe() 方法告知观测器要监听哪些类型的条目:

const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

po.observe({type: 'some-entry-type'});

以下各部分列出了可用于观测的所有各种条目类型,但在较新的浏览器中,您可以通过静态 PerformanceObserver.supportedEntryTypes 属性检查可用的条目类型。

查看已发生的条目

默认情况下,PerformanceObserver 对象只能在条目发生时对其进行观测。如果您想延迟加载性能分析代码,以免其阻塞优先级较高的资源,这可能会导致问题。

如需获取历史条目(在条目发生后),请在调用 observe() 时将 buffered 标志设置为 true。浏览器会在首次调用 PerformanceObserver 回调时,从其性能条目缓冲区中包含历史条目,最多包含该类型的最大缓冲区大小

po.observe({
  type: 'some-entry-type',
  buffered: true,
});

应避免使用的旧版性能 API

在 Performance Observer API 推出之前,开发者可以使用 performance 对象上定义的以下三种方法来访问性能条目:

虽然这些 API 仍受支持,但不建议使用,因为它们不允许您监听何时发出新条目。此外,许多新 API(例如 largest-contentful-paint)不会通过 performance 对象公开,而只会通过 PerformanceObserver 公开。

除非您特别需要与 Internet Explorer 兼容,否则最好避免在代码中使用这些方法,并从现在开始使用 PerformanceObserver

User Timing API

Browser Support

  • Chrome: 28.
  • Edge: 12.
  • Firefox: 38.
  • Safari: 11.

Source

用户计时 API 是一种用于基于时间的指标的通用测量 API。您可以随意标记时间点,然后在稍后测量这些标记之间的时长。

// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();

// Record the time immediately after running a task.
performance.mark('myTask:end');

// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');

虽然 Date.now()performance.now() 等 API 也可提供类似的功能,但使用 User Timing API 的好处在于,它可以与性能分析工具很好地集成。例如,Chrome 开发者工具会在“性能”面板中直观呈现用户计时测量结果,许多分析服务提供商也会自动跟踪您进行的任何测量,并将时长数据发送到其分析后端。

如需报告用户计时测量结果,您可以使用 PerformanceObserver 并注册以观察类型为 measure 的条目:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});

Long Tasks API

Browser Support

  • Chrome: 58.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Long Tasks API 可用于了解浏览器的主线程何时被阻塞的时间长到足以影响帧速率或输入延迟。该 API 将报告执行时间超过 50 毫秒的所有任务。

每当您需要运行开销较大的代码或加载并执行大型脚本时,最好跟踪该代码是否会阻塞主线程。事实上,许多更高级别的指标本身就是基于 Long Tasks API 构建的(例如 Time to Interactive (TTI)Total Blocking Time (TBT))。

如需确定何时发生长时间任务,您可以使用 PerformanceObserver 并注册以观察类型为 longtask 的条目:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});

Long Animation Frames API

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

Long Animation Frames API 是 Long Tasks API 的新迭代版本,它着眼于超过 50 毫秒的长帧,而不是长任务。这解决了 Long Tasks API 的一些缺点,包括更好的归因和更广泛的潜在问题延迟范围。

如需确定何时发生长帧,您可以使用 PerformanceObserver 并注册以观察类型为 long-animation-frame 的条目:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});

Element Timing API

Browser Support

  • Chrome: 77.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Largest Contentful Paint (LCP) 指标有助于了解最大图片或文本块何时绘制到屏幕上,但在某些情况下,您可能需要衡量其他元素的渲染时间。

对于这些情况,请使用 Element Timing API。LCP API 实际上是基于 Element Timing API 构建的,它会添加对最大内容元素 (LCP) 的自动报告,但您也可以通过显式向其他元素添加 elementtiming 属性并注册 PerformanceObserver 来观察 element 条目类型,从而报告这些元素。

<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->

<script>
  const po = new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  // Start listening for `element` entries to be dispatched.
  po.observe({type: 'element', buffered: true});
</script>

Event Timing API

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 89.
  • Safari: 26.2.

Source

Interaction to Next Paint (INP) 指标通过观测网页生命周期内的所有点击、点按和键盘互动,评估网页的总体响应情况。网页的 INP 通常是指从用户发起互动到浏览器绘制下一个帧(显示用户输入的可视结果)之间用时最长的互动。

INP 指标是通过 Event Timing API 实现的。此 API 会公开活动生命周期内发生的多个时间戳,包括:

  • startTime:浏览器收到事件的时间。
  • processingStart:浏览器能够开始处理相应事件的事件处理程序的时间。
  • processingEnd:浏览器完成执行由此事件的事件处理程序启动的所有同步代码的时间。
  • duration:浏览器收到事件到能够绘制下一帧(在完成执行从事件处理脚本启动的所有同步代码之后)之间的时间(出于安全原因,四舍五入到 8 毫秒)。

以下示例展示了如何使用这些值创建自定义衡量指标:

const po = new PerformanceObserver((entryList) => {
  // Get the last interaction observed:
  const entries = Array.from(entryList.getEntries()).forEach((entry) => {
    // Get various bits of interaction data:
    const inputDelay = entry.processingStart - entry.startTime;
    const processingTime = entry.processingEnd - entry.processingStart;
    const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
    const duration = entry.duration;
    const eventType = entry.name;
    const target = entry.target || "(not set)"

    console.log("----- INTERACTION -----");
    console.log(`Input delay (ms): ${inputDelay}`);
    console.log(`Event handler processing time (ms): ${processingTime}`);
    console.log(`Presentation delay (ms): ${presentationDelay}`);
    console.log(`Total event duration (ms): ${duration}`);
    console.log(`Event type: ${eventType}`);
    console.log(target);
  });
});

// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});

Resource Timing API

Browser Support

  • Chrome: 29.
  • Edge: 12.
  • Firefox: 35.
  • Safari: 11.

Source

借助 Resource Timing API,开发者可以详细了解特定网页的资源是如何加载的。尽管该 API 的名称中包含“时间”一词,但它提供的信息不仅限于时间数据(尽管此类数据非常丰富)。您还可以访问以下其他数据:

  • initiatorType:资源的提取方式,例如通过 <script><link> 标记提取,或者通过 fetch() 调用提取。
  • nextHopProtocol:用于提取资源的协议,例如 h2quic
  • encodedBodySize/decodedBodySize:资源以编码或解码形式存在时的大小(分别)
  • transferSize:通过网络实际传输的资源的大小。如果资源由缓存提供,此值可以远小于 encodedBodySize,在某些情况下甚至可以为零(如果不需要重新验证缓存)。

您可以使用资源时间条目的 transferSize 属性来衡量缓存命中率指标或缓存资源总大小指标,这有助于了解资源缓存策略对回头客的性能有何影响。

以下示例会记录网页请求的所有资源,并指明缓存是否满足了每个资源请求。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log(entry.name, entry.transferSize === 0);
  }
});

// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});

Browser Support

  • Chrome: 57.
  • Edge: 12.
  • Firefox: 58.
  • Safari: 15.

Source

导航计时 API 与资源计时 API 类似,但它仅报告导航请求navigation 条目类型也类似于 resource 条目类型,但它包含一些仅针对导航请求的附加信息(例如在触发 DOMContentLoadedload 事件时)。

许多开发者都会跟踪一个指标,以了解服务器响应时间(首字节时间 (TTFB)),该指标可通过 Navigation Timing API 获取,具体来说是条目的 responseStart 时间戳。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log('Time to first byte', entry.responseStart);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});

使用 Service Worker 的开发者可能还会关注导航请求的 Service Worker 启动时间。这是浏览器启动 Service Worker 线程所需的时间,之后才能开始拦截提取事件。

特定导航请求的 Service Worker 启动时间可以通过 entry.responseStartentry.workerStart 之间的差值来确定。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Service Worker startup time:',
        entry.responseStart - entry.workerStart);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});

Server Timing API

Browser Support

  • Chrome: 65.
  • Edge: 79.
  • Firefox: 61.
  • Safari: 16.4.

Source

借助 Server Timing API,您可以通过响应标头将特定于请求的时序数据从服务器传递到浏览器。例如,您可以指明查找特定请求的数据库中的数据所花费的时间,这有助于调试因服务器速度缓慢而导致的性能问题。

对于使用第三方分析提供商的开发者来说,Server Timing API 是将服务器性能数据与这些分析工具可能衡量的其他业务指标相关联的唯一方法。

如需在响应中指定服务器计时数据,您可以使用 Server-Timing 响应标头。示例如下:

HTTP/1.1 200 OK

Server-Timing: miss, db;dur=53, app;dur=47.2

然后,您就可以从网页中读取 Resource Timing 和 Navigation Timing API 中 resourcenavigation 条目的这些数据。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Logs all server timing data for this response
    console.log('Server Timing', entry.serverTiming);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});