User Timing API

了解您的 Web 应用

Alex Danilo

高性能 Web 应用对于提供出色的用户体验至关重要。随着 Web 应用变得越来越复杂,了解性能影响对于打造引人入胜的体验至关重要。过去几年,浏览器中出现了许多不同的 API,用于帮助分析网络性能、加载时间等,但这些 API 不一定能提供足够灵活的精细细节,以便找出导致应用速度变慢的原因。输入 User Timing API,该 API 提供了一种机制,可用于对 Web 应用进行插桩,以确定应用在哪些方面花费了时间。在本文中,我们将介绍该 API 以及如何使用它的示例。

无法衡量,便无法优化

要加快 Web 应用的运行速度,第一步是找出时间花在哪里。衡量 JavaScript 代码区域的时间影响是确定热点的理想方法,也是了解如何提升性能的第一步。幸运的是,User Timing API 提供了一种方法,让您可以在 JavaScript 的不同部分插入 API 调用,然后提取详细的用户计时数据,这些数据可用于帮助您进行优化。

高分辨率时间和 now()

准确测量时间的一个基本要素是精度。过去,我们采用基于毫秒测量的计时方式,这没问题,但要构建一个无卡顿的 60 FPS 网站,意味着每帧都需要在 16 毫秒内绘制完成。因此,如果只有毫秒级精度,就无法满足进行良好分析所需的精度。高分辨率时间是一种内置于现代浏览器中的新计时类型。高分辨率时间为我们提供了浮点时间戳,其精度可达到微秒级,比以前提高了 1,000 倍。

如需在 Web 应用中获取当前时间,请调用 now() 方法,该方法构成了 Performance 接口的扩展。以下代码展示了如何执行此操作:

var myTime = window.performance.now();

另一个名为 PerformanceTiming 的接口可提供与 Web 应用加载方式相关的多个不同时间。now() 方法会返回自 PerformanceTiming 中的 navigationStart 时间发生后经过的时间。

DOMHighResTimeStamp 类型

过去,在尝试对 Web 应用进行计时时,您会使用 Date.now() 之类的函数,该函数会返回 DOMTimeStamp。DOMTimeStamp 返回一个整数毫秒数作为其值。为了提供高分辨率时间所需的更高准确度,我们引入了一种名为 DOMHighResTimeStamp 的新类型。此类型是一个浮点值,也会返回以毫秒为单位的时间。但由于它是浮点数,因此该值可以表示毫秒的小数部分,从而可以达到千分之一毫秒的精确度。

User Timing 接口

现在我们已经有了高分辨率时间戳,接下来就使用 用户计时 接口来提取计时信息。

用户计时接口提供的函数可让我们在应用中的不同位置调用方法,这些方法可提供 Hansel 和 Gretel 样式的面包屑导航,让我们能够跟踪时间花在何处。

使用 mark()

mark() 方法是我们的计时分析工具包中的主要工具。mark() 的作用是为我们存储时间戳。mark() 的一个非常实用的功能是,我们可以为时间戳命名,API 会将名称和时间戳作为一个单元记住。

在应用中的不同位置调用 mark(),可让您计算出在 Web 应用中达到该“标记”所用的时间。

该规范列出了许多建议的标记名称,这些名称可能很有趣,而且不言自明,例如 mark_fully_loaded、mark_fully_visible、mark_above_the_fold 等。

例如,我们可以使用以下代码为应用完全加载的时间设置一个标记:

window.performance.mark('mark_fully_loaded');

通过在整个 Web 应用中设置命名标记,我们可以收集大量计时数据,并在闲暇时进行分析,以了解应用在何时执行了哪些操作。

正在使用measure()计算测量结果

设置好一系列时间标记后,您需要找出它们之间的经过时间。您可以使用 measure() 方法来执行此操作。

measure() 方法可计算标记之间经过的时间,还可以测量标记与 PerformanceTiming 接口中任何已知事件名称之间的时间。

例如,您可以使用如下代码计算从 DOM 完成到应用状态完全加载的时间:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

当您调用 measure() 时,它会存储结果,而不管您设置的标记如何,以便您稍后检索。通过在应用运行时存储时间,应用可以保持响应,并且您可以在应用完成某些工作后转储所有数据,以便稍后进行分析。

使用 clearMarks() 舍弃标记

有时,能够清除您设置的一堆标记会很有用。例如,您可能需要对 Web 应用进行批量运行,因此希望每次运行都从头开始。

通过调用 clearMarks(),可以轻松移除您设置的所有标记。

因此,以下示例代码会清除所有现有标记,以便您在需要时重新设置计时运行。

window.performance.clearMarks();

当然,在某些情况下,您可能不想清除所有标记。因此,如果您想移除特定标记,只需传递要移除的标记的名称即可。例如,以下代码:

window.performance.clearMarks('mark_fully_loaded');

会移除我们在第一个示例中设置的标记,同时保持我们设置的所有其他标记不变。

您可能还想删除已创建的所有测量,有一个名为 clearMeasures() 的相应方法可以做到这一点。它的工作原理与 clearMarks() 完全相同,但适用于您进行的任何测量。例如,以下代码:

window.performance.clearMeasures('measure_load_from_dom');

将移除我们在上述 measure() 示例中创建的测量。如果您想移除所有测量,其工作方式与 clearMarks() 相同,即只需调用不带参数的 clearMeasures()。

获取计时数据

设置标记和测量间隔固然很好,但有时您可能希望获取这些计时数据以执行一些分析。这也非常简单,您只需使用 PerformanceTimeline 接口即可。

例如,通过 getEntriesByType() 方法,我们可以获取所有标记时间或所有测量时间,并以列表形式返回,以便我们对其进行迭代并消化数据。好消息是,该列表会按时间顺序返回,因此您可以按照在 Web 应用中命中的顺序查看标记。

以下代码:

var items = window.performance.getEntriesByType('mark');

会返回 Web 应用中已命中的所有标记的列表,而以下代码:

var items = window.performance.getEntriesByType('measure');

返回我们所做的所有测量的列表。

您还可以使用您为条目指定的特定名称来获取条目列表。例如,以下代码:

var items = window.performance.getEntriesByName('mark_fully_loaded');

会返回一个列表,其中包含一个项,该项在 startTime 属性中包含“mark_fully_loaded”时间戳。

为 XHR 请求计时(示例)

现在,我们已经对 User Timing API 有了充分的了解,可以使用它来分析 Web 应用中所有 XMLHttpRequests 所用的时间。

首先,我们将修改所有 send() 请求,以发出设置标记的函数调用,同时将成功回调更改为设置另一个标记的函数调用,然后生成请求所用时长的测量结果。

因此,我们的 XMLHttpRequest 通常如下所示:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

在本示例中,我们将添加一个全局计数器来跟踪请求数量,并使用该计数器来存储每个请求的测量值。执行此操作的代码如下所示:

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

上述代码会为我们发送的每个 XMLHttpRequest 生成一个具有唯一名称值的测量。我们假设请求是按顺序运行的,而并行请求的代码需要稍微复杂一些,以便处理不按顺序返回的请求,我们将其留给读者作为练习。

Web 应用完成一系列请求后,我们可以使用以下代码将所有请求转储到控制台:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

总结

User Timing API 提供了许多出色的工具,可应用于 Web 应用的任何方面。通过在整个 Web 应用中分散 API 调用并对生成的时间数据进行后处理,您可以轻松缩小应用中的热点范围,从而清楚地了解时间花在哪里。但如果您的浏览器不支持此 API,该怎么办?没问题,您可以在此处找到一个出色的 polyfill,它能很好地模拟该 API,并且与 webpagetest.org 也能很好地配合。还在等什么呢?立即在您的应用中试用 User Timing API,您将了解如何提高应用速度,而用户也会感谢您为他们带来更出色的体验。