Performance API不完全使用指北

2023-02-13 12:11:37 浏览数 (1)

本教程解释了如何使用Performance API来记录真实用户访问你的应用程序的统计数据。

使用浏览器的DevTools来评估web应用性能是很有用的,但要复现现实世界的使用情况并不容易。因为人们在不同地点使用不同的设备、浏览器和网络,都会有不同的体验。

Performance API介绍

Performance API[1]使用一个缓冲区,在你的网页生命周期的确定节点上,在对象属性中记录类似DevTool的指标。这些节点包括:

  1. 页面导航:记录页面加载重定向、连接、握手、DOM事件等等。
  2. 资源加载:记录资源加载,比如图像、CSS、脚本以及Ajax调用。
  3. 绘制指标:记录浏览器渲染信息。
  4. 自定义:记录任意的应用处理时间,来找到运行慢的函数。

所有的API都可以在客户端的JavaScript中使用,包括Web Workers[2]。你可以用以下方法检测API支持情况:

代码语言:javascript复制
if ('performance' in window) {
  // call Performance APIs
}

注意:尽管Safari实现了大部分的API,但Safari并不支持所有的方法。

自定义performance API也被复制到了:

  • Node.js 内置performance_hook模块,以及
  • Deno performance API[3],(使用它的脚本必须以 --allow-hrtime权限运行)。

Date()不够好吗

你可能已经看到过使用Date()函数来记录经过时间的例子。比如:

代码语言:javascript复制
const start = new Date();

// ... run code ...

const elapsed = new Date() - start;

然而,Date()的计算被限制在最接近的毫秒数,并且是基于系统时间。而系统时间可以在任何时候被操作系统更新。

Performance API使用独立的、高精度的定时器,其可以在几毫秒的时间内记录。它还提供其他方式无法记录的指标,如重定向和DNS查询时间。

记录性能指标

如果你可以在某处记录的话,在客户端代码中记录性能指标是非常有用的。你可以使用Fetch[4]/XMLHttpRequest[5]请求,或者使用Beacon API[6],来发送统计数据到服务端进行分析。

另外,大多数分析系统提供类似的事件API来记录时间。比如说,Google分析的User Timings API[7]可以通过传递类别'pageload'、变量名'DOMready'和一个值,来记录DOMContentLoaded的时间:

代码语言:javascript复制
const pageload = performance.getEntriesByType( 'navigation' )[0];

ga('send', 'timing', 'pageload', 'DOMready', pageload.domContentLoadedEventStart);

这个例子使用了Page Navigation Timing API,那么就从这开始吧。

页面导航时间

在快速连接上测试你的网站,并不能代表用户体验。浏览器DevTools的NetWork标签允许你限制速度,但它不能模拟糟糕的或间歇性的信号。

Navigation Timing API将单独的PerformanceNavigationTiming[8]对象放入到性能缓冲区中。它包含有关重定向、加载时间、文件大小、DOM事件等的信息。

通过运行以下代码来访问该对象:

代码语言:javascript复制
const pagePerf = performance.getEntriesByType('navigation');

或者传递页面URL(window.location)到 getEntriesByName()[9] 方法中,来访问该对象:

代码语言:javascript复制
const pagePerf = performance.getEntriesByName(window.location);

两者都返回一个数组,该数组拥有一个具有只读属性的对象的单一元素。比如说:

代码语言:javascript复制
[
  {
    name: "<https://site.com/>",
    initiatorType: "navigation",
    entryType: "navigation",
    initiatorType: "navigation",
    type: "navigate",
    nextHopProtocol: "h2",
    startTime: 0
    ...
  }
]

该对象包含资源识别属性:

属性

描述

name

资源URL

entryType

性能类型 — "navigation"代表一个页面,"resource"代表一个资源

initiatorType

启动下载的资源 — "navigation"代表一个页面

nextHopProtocol

网络协议

serverTiming

PerformanceServerTiming对象数组

注意:performanceServerTimingnamedescriptionduration等指标由服务器响应写入HTTPServer-Timing[10]头部。

该对象包括相对于页面加载开始的以毫秒为单位的资源时间属性。通常情况下,时间会按照这个顺序来展示:

属性

描述

startTime

页面开始获取时的时间戳,从0开始

workerStart

启动Service Worker之前的时间戳

redirectStart

首次重定向的时间戳

redirectEnd

收到最后重定向最后一个字节后的时间戳

fetchStart

资源开始获取前的时间戳

domainLookupStart

DNS查询前的时间戳

domainLookupEnd

DNS查询后的时间戳

connectStart

建立服务器连接前的时间戳

connectEnd

建立服务器连接后的时间戳

secureConnectionStart

SSL握手前的时间戳

requestStart

浏览器请求前的时间戳

responseStart

浏览器收到第一个字节数据的时间戳

responseEnd

收到最后一个字节数据后的时间戳

duration

从startTime到responseEnd所经过的时间

该对象包括以字节为单位的下载大小属性:

属性

描述

transferSize

资源大小,包括头部和主体

encodedBodySize

解压前的资源主体大小

decodedBodySize

解压后的资源主体大小

最后,该对象包括进一步的导航和DOM事件属性(在Safari中不可用):

属性

描述

type

"navigate"、"reload"、"back_forward"

或者 "prerender"

redirectCount

重定向的次数

unloadEventStart

前一个文档的unload事件之前的时间戳

unloadEventEnd

前一个文档的unload事件之后的时间戳

domInteractive

HTML解析和DOM构建完成时的时间戳

domContentLoadedEventStart

运行DOMContentLoaded事件处理器前的时间戳

domContentLoadedEventEnd

运行DOMContentLoaded事件处理器后的时间戳

domComplete

DOM构建和DOMContentLoaded事件完成后的时间戳

loadEventStart

页面load事件发生前的时间戳

loadEventEnd

页面load事件发生后的时间戳,所有资源已经被下载

在页面完全加载后记录页面加载指标的例子如下:

代码语言:javascript复制
'performance' in window && window.addEventListener('load', () => {

  const
    pagePerf        = performance.getEntriesByName(window.location)[0],
    pageDownload    = pagePerf.duration,
    pageDomComplete = pagePerf.domComplete;

});

页面资源时间

每当页面加载图片、字体、CSS文件、JavaScript文件等资产时,Resource Timing API将PerformanceResourceTiming[11]对象放入性能缓冲区中,可以这么运行:

代码语言:javascript复制
const resPerf = performance.getEntriesByType('resource');

这样会返回资源时间的对象数组。这些属性与上面显示的页面时间相同,但没有导航和DOM事件信息。

下面是返回结果的示例:

代码语言:javascript复制
[
  {
    name: "<https://site.com/style.css>",
    entryType: "resource",
    initiatorType: "link",
    fetchStart: 150,
    duration: 300
    ...
  },
  {
    name: "<https://site.com/script.js>",
    entryType: "resource",
    initiatorType: "script",
    fetchStart: 302,
    duration: 112
    ...
  },
  ...
]

单一资源可以传递资源URL到.getEntriesByName()方法进行测试:

代码语言:javascript复制
const resourceTime = performance.getEntriesByName('<https://site.com/style.css>');

这会返回单一元素的数组:

代码语言:javascript复制
[
  {
    name: "<https://site.com/style.css>",
    entryType: "resource",
    initiatorType: "link",
    fetchStart: 150,
    duration: 300
    ...
  }
]

可以使用API来报告加载时间以及每个CSS文件解压后的大小:

代码语言:javascript复制
// array of CSS files, load times, and file sizes
const css = performance.getEntriesByType('resource')
  .filter(r => r.initiatorType === 'link' && r.name.includes('.css'))
  .map(r => ({

      name: r.name,
      load: r.duration   'ms',
      size: r.decodedBodySize   ' bytes'

  }));

CSS数组现在为每个CSS文件包含一个对象。比如:

代码语言:javascript复制
[
  {
    name: "<https://site.com/main.css>",
    load: "155ms",
    size: "14304 bytes"
  },
  {
    name: "<https://site.com/grid.css>",
    load: "203ms",
    size: "5696 bytes"
  }
]

注意:load的大小为0表示该资源已经被缓存了。

至少有150个资源指标对象将被记录到性能缓冲区。你可以用.setResourceTimingBufferSize(N)方法定义一个指定数字。比如:

代码语言:javascript复制
// record 500 resources
performance.setResourceTimingBufferSize(500);

现有的指标可以用.clearResourceTimings()方法清除。

浏览器绘制时间

First Contentful Paint (FCP)[12]测量用户导航到你的页面后渲染内容所需的时间。Chrome的DevTool的Lighthouse标签展示了该指标。谷歌认为FCP时间少于两秒是好的,你的页面将比75%的页面展现的更快。

当发生以下两种情况时,Paint Timing API将两个记录也就是两个PerformancePaintTiming[13]对象推入性能缓冲区:

  • first-paint发生:浏览器绘制首个像素,以及
  • first-contentful-paint发生:浏览器绘制首个DOM元素

当运行下面代码时,两个对象以数组形式返回:

代码语言:javascript复制
const paintPerf = performance.getEntriesByType('paint');

返回结果示例:

代码语言:javascript复制
[
  {
    "name": "first-paint",
    "entryType": "paint",
    "startTime": 125
  },
  {
    "name": "first-contentful-paint",
    "entryType": "paint",
    "startTime": 127
  }
]

startTime是相对于初始化页面加载的时间。

用户时间

Performance API可以用来为你自己的应用功能计时。所有的用户时间方法都可以在客户端的JavaScript、Web Workers、Deno和Node.js中使用。

注意,Node.js脚本必须加载Performance hooks[14](perf_hooks)模块。**

CommonJSrequire语法:

代码语言:javascript复制
const { performance } = require('perf_hooks');

或者ESMimport语法:

代码语言:javascript复制
import { performance } from 'perf_hooks';

最简单的选择是[performance.now()](<https://developer.mozilla.org/docs/Web/API/Performance/now> "performance.now()"),其会从程序的生命周期开始,返回一个高精度时间戳。

你可以使用performance.now()作为简单的计时器。比如说:

代码语言:javascript复制
const start = performance.now();

// ... run code ...

const elapsed = performance.now() - start;

注意,不标准的timeOrigin属性返回一个时间戳。可以用于Node.js和浏览器JavaScript,但不能用于IE和Safari。

当管理多个定时器时,performance.now()很快就变得不切实际。.mark()[15]方法添加一个名为PerformanceMark object[16]对象到性能缓冲区。比如说:

代码语言:javascript复制
performance.mark('script:start');

performance.mark('p1:start');
// ... run process 1 ...
performance.mark('p1:end');

performance.mark('p2:start');
// ... run process 2 ...
performance.mark('p2:end');

performance.mark('script:end');

下列代码返回mark对象数组:

代码语言:javascript复制
const marks = performance.getEntriesByType('mark');

数组里的对象拥有entryTypenamestartTime属性:

代码语言:javascript复制
[
  {
    entryType: "mark",
    name: "script:start",
    startTime: 100
  },
  {
    entryType: "mark",
    name: "p1:start",
    startTime: 200
  },
  {
    entryType: "mark",
    name: "p1:end",
    startTime: 300
  },
  ...
]

两个标记之间的时间可以用.measure()[17]方法来计算。它传递一个测量名称,开始标记名称(或者null),以及结束标记名称(或者null):

代码语言:javascript复制
performance.measure('p1', 'p1:start', 'p1:end');
performance.measure('script', null, 'script:end');

每次调用都会向性能缓冲区推送一个带有计算持续时间的PerformanceMeasure[18]对象。测量数组可以通过运行以下代码进行访问:

代码语言:javascript复制
const measures = performance.getEntriesByType('measure');

返回示例:

代码语言:javascript复制
[
  {
    entryType: "measure",
    name: "p1",
    startTime: 200,
    duration: 100
  },
  {

    entryType: "measure",
    name: "script",
    startTime: 0,
    duration: 500
  }
]

标记或测量对象可以使用.getEntriesByName()[19]方法按名称检索:

代码语言:javascript复制
performance.getEntriesByName('p1');

其他方法:

  • .getEntries()[20]:返回所有性能条目的数组。
  • .clearMarks([name])[21]:清除指定名称的标记(不指定名称则清除所有标记)。
  • .clearMeasures([name])[22]:清除指定名称的测量(不指定名称则清除所有测量)。

PerformanceObserver[23]可以监听缓冲区的更改,当指定对象出现时执行函数。观察者函数使用两个参数定义:

  1. list:观察者条目
  2. observer(可选):观察者对象
代码语言:javascript复制
function performanceHandler(list, observer) {

  list.getEntries().forEach(entry => {

    console.log(`name    : ${ entry.name }`);
    console.log(`type    : ${ entry.type }`);
    console.log(`duration: ${ entry.duration }`);

    // other code, e.g.
    // send data via an Ajax request

  });

}

该函数传递一个新的PerformanceObserver对象。.observe()[24]方法设置可观察的entryTypes(一般来说是markmeasure或者resource):

代码语言:javascript复制
let observer = new PerformanceObserver(performanceHandler);
observer.observe({entryTypes: ['mark', 'measure']});

每当有新的标记或测量对象被推送到性能缓冲区,performanceHandler()函数就会运行。

总结

Performance API提供了一种方法来测量网站和应用程序的速度,这些设备是由不同地点的人在一系列连接上使用的实际设备。它使每个人都能轻松地整理出类似DevTool的指标,并识别潜在的瓶颈。

  • 本文译自:https://www.sitepoint.com/performance-api-faster-websites-apps/
  • 作者:Craig Buckler

以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~

参考资料

[1]

Performance API: https://developer.mozilla.org/docs/Web/API/Performance_API

[2]

Web Workers: https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Using_web_workers

[3]

performance API: https://doc.deno.land/builtin/stable#Performance

[4]

Fetch: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API

[5]

XMLHttpRequest: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

[6]

Beacon API: https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API

[7]

User Timings API: https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings

[8]

PerformanceNavigationTiming: https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming

[9]

getEntriesByName(): https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByName

[10]

Server-Timing: https://developer.mozilla.org/docs/Web/HTTP/Headers/Server-Timing

[11]

PerformanceResourceTiming: https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming

[12]

First Contentful Paint (FCP): https://web.dev/first-contentful-paint/

[13]

PerformancePaintTiming: https://developer.mozilla.org/docs/Web/API/PerformancePaintTiming

[14]

Performance hooks: https://nodejs.org/dist/latest/docs/api/perf_hooks.html

[15]

.mark(): https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/mark

[16]

PerformanceMark object: https://developer.mozilla.org/docs/Web/API/PerformanceMark

[17]

.measure(): https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/measure

[18]

PerformanceMeasure: https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceMeasure

[19]

.getEntriesByName(): https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/getEntriesByName

[20]

.getEntries(): https://developer.mozilla.org/docs/Web/API/Performance/getEntries

[21]

.clearMarks([name]): https://developer.mozilla.org/docs/Web/API/Performance/clearMarks

[22]

.clearMeasures([name]): https://developer.mozilla.org/docs/Web/API/Performance/clearMeasures

[23]

PerformanceObserver: https://developer.mozilla.org/docs/Web/API/PerformanceObserver

[24]

.observe(): https://developer.mozilla.org/docs/Web/API/PerformanceObserver/observe

0 人点赞