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 界面提取时间信息。

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 有了充分的了解,可以使用它来分析 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,您将了解如何加快应用的速度,您的用户也会因为您改善了他们的体验而感谢您。