了解您的 Web 应用
高性能 Web 应用对于提供出色的用户体验至关重要。随着 Web 应用变得越来越复杂,了解性能影响对于打造引人入胜的体验至关重要。在过去几年里,浏览器中出现了许多不同的 API,可帮助分析网络性能、加载时间等,但这些 API 不一定能提供足够灵活的精细细节,以便找出导致应用运行缓慢的原因。介绍 User Timing API,该 API 提供了一种机制,可用于插桩 Web 应用,以确定应用在哪些方面花费了时间。本文将介绍该 API 以及使用该 API 的示例。
无法衡量也就意味着无从改进
若要加快运行缓慢的 Web 应用的速度,第一步是找出时间花在哪里。衡量 JavaScript 代码各个区域的时间影响是确定热点的理想方式,而确定热点是找出如何提升性能的第一步。幸运的是,User Timing API 提供了一种方法,可让您在 JavaScript 的不同部分插入 API 调用,然后提取可用于帮助您进行优化的详细时间数据。
高分辨率时间和 now()
精确是准确测量时间的基本要素。以前,我们根据毫秒测量时间,这没问题,但要打造无卡顿的 60 FPS 网站,则需要在 16 毫秒内绘制每一帧。因此,如果精确到毫秒,则缺乏进行良好分析所需的精确度。输入高分辨率时间,这是内置于新型浏览器中的一种新计时类型。高分辨率时间能够为我们提供了浮点时间戳,这些时间戳可精确到微秒,是以前的一千倍之多。
如需获取 Web 应用中的当前时间,请调用 now()
方法,该方法构成了 Performance 接口的扩展。以下代码展示了如何执行此操作:
var myTime = window.performance.now();
还有一个名为 PerformanceTiming 的界面,它提供了许多与网页应用的加载方式相关的时间。now()
方法会返回从 PerformanceTiming 中的 navigationStart
时间开始所经过的时间。
DOMHighResTimeStamp 类型
过去,在尝试对 Web 应用进行计时时,您会使用 Date.now()
等会返回 DOMTimeStamp 的函数。DOMTimeStamp 会返回一个整数毫秒数作为其值。为了提供高分辨率时间所需的更高的精度,我们引入了一种名为 DOMHighResTimeStamp 的新类型。该类型是一个浮点值,同样返回以毫秒为单位的时间。但由于它是浮点值,因此可以表示毫秒的小数部分,因此可以达到百分之一毫秒的精度。
用户计时接口
现在,我们已经有了高分辨率的时间戳,接下来使用 User Timing 界面提取时间信息。
User Timing 接口提供了一些函数,可让我们在应用的不同位置调用方法,这些方法可以提供汉泽尔和格雷特风格的面包屑导航,以便我们跟踪时间的消耗情况。
使用 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 应用中散布 API 调用并对生成的时间数据进行后处理,以清楚了解时间花费在何处,可以轻松缩小应用中的热点范围。但如果您的浏览器不支持此 API 呢?没关系,您可以在这里找到一个很棒的 polyfill,它可以很好地模拟 API,并且与 webpagetest.org 搭配使用也很不错。还在等什么呢?立即在您的应用中试用 User Timing API,您将了解如何加快应用的速度,您的用户也会因为您改善了他们的体验而感谢您。