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 毫秒内完成绘制。因此,如果精确率只有毫秒级,就会缺乏进行良好分析所需的精度。输入高分辨率时间,这是现代浏览器中内置的一种新时间类型。高分辨率时间可为我们提供浮点时间戳,其分辨率精确到微秒,比以前好一千倍。

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

var myTime = window.performance.now();

还有一个名为 PerformanceTiming 的接口,该接口提供了与 Web 应用的加载方式相关的多个不同时间。now() 方法会返回 PerformanceTiming 中出现 navigationStart 时间所经过的时间。

DOMHighResTimeStamp 类型

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

User Timing 接口

现在,由于我们拥有高分辨率的时间戳,我们可以使用“User Timing”界面提取时间信息。

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.peformance.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_full_loaded”时间戳的项。

为 XHR 请求计时(示例)

现在,我们对 User Timing API 有了充分的了解,接下来可以使用它来分析所有 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 生成一个具有唯一 name 值的测量。我们假设请求是按顺序运行,并行请求的代码需要稍微复杂一点,以处理不按顺序返回的请求。我们在��里让读者练习一下。

在 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,您将找出如何提升其速度,您的用户会感谢您改善其体验。