User Timing API

了解您的 Web 应用

Alex Danilo

高性能 Web 应用对于提供出色的用户体验至关重要。随着 Web 应用变得越来越复杂,了解性能影响对于打造引人入胜的体验至关重要。在过去的几年里,浏览器中出现了许多不同的 API,以帮助分析网络性能、加载时间等,但这些 API 不一定能提供足够详细的、足够的灵活性,以找出导致应用速度变慢的因素。介绍 User Timing API,该 API 提供了一种机制,可用于插桩 Web 应用,以确定应用在哪些方面花费了时间。本文将介绍该 API 以及使用该 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 接口提供了一些函数,可让我们在应用的不同位置调用方法,这些方法可以提供汉泽尔和格雷特风格的面包屑导航,以便我们跟踪时间的消耗情况。

使用 mark()

mark() 方法是时间分析工具包中的主要工具。mark() 的作用是为我们存储时间戳。mark() 的一大优势在于,我们可以为时间戳命名,而 API 会将名称和时间戳作为一个单元进行记忆。

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

规范中列出了一些可能有趣且自解释性较强的标记建议名称,例如 mark_fully_loadedmark_fully_visiblemark_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 后,我们就可以使用它来分析所有 XMLHttpRequests 在 Web 应用中花费的时间。

首先,我们将修改所有 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,您将了解如何加快应用的速度,您的用户也会因为您改善了他们的体验而感谢您。