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

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

Performance API介绍

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

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

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

if ('performance' in window) {
// call Performance APIs
}

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

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

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

Date()不够好吗

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

const start = new Date();

// ... run code ...

const elapsed = new Date() - start;

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

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

记录性能指标

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

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

const pageload = performance.getEntriesByType( 'navigation' )[0];

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

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

页面导航时间

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

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

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

const pagePerf = performance.getEntriesByType('navigation');

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

const pagePerf = performance.getEntriesByName(window.location);

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

[
{
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头部。

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

属性 描述
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事件发生后的时间戳,所有资源已经被下载

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

'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对象放入性能缓冲区中,可以这么运行:

const resPerf = performance.getEntriesByType('resource');

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

下面是返回结果的示例:

[
{
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()方法进行测试:

const resourceTime = performance.getEntriesByName('<https://site.com/style.css>');

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

[
{
name: "<https://site.com/style.css>",
entryType: "resource",
initiatorType: "link",
fetchStart: 150,
duration: 300
...
}
]

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

// 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文件包含一个对象。比如:

[
{
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)方法定义一个指定数字。比如:

// record 500 resources
performance.setResourceTimingBufferSize(500);

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

浏览器绘制时间

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

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

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

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

const paintPerf = performance.getEntriesByType('paint');

返回结果示例:

[
{
"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 hooksperf_hooks)模块。**

CommonJSrequire语法:

const { performance } = require('perf_hooks');

或者ESMimport语法:

import { performance } from 'perf_hooks';

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

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

const start = performance.now();

// ... run code ...

const elapsed = performance.now() - start;

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

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

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对象数组:

const marks = performance.getEntriesByType('mark');

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

[
{
entryType: "mark",
name: "script:start",
startTime: 100
},
{
entryType: "mark",
name: "p1:start",
startTime: 200
},
{
entryType: "mark",
name: "p1:end",
startTime: 300
},
...
]

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

performance.measure('p1', 'p1:start', 'p1:end');
performance.measure('script', null, 'script:end');

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

const measures = performance.getEntriesByType('measure');

返回示例:

[
{
entryType: "measure",
name: "p1",
startTime: 200,
duration: 100
},
{ entryType: "measure",
name: "script",
startTime: 0,
duration: 500
}
]

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

performance.getEntriesByName('p1');

其他方法:

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

  1. list:观察者条目
  2. observer(可选):观察者对象
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()方法设置可观察的entryTypes(一般来说是markmeasure或者resource):

let observer = new PerformanceObserver(performanceHandler);
observer.observe({entryTypes: ['mark', 'measure']});

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

总结

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

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

Performance API不完全使用指北的更多相关文章

  1. 后端API入门到放弃指北

    后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一 ...

  2. Python 简单入门指北(二)

    Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...

  3. 可能比文档还详细--VueRouter完全指北

    可能比文档还详细--VueRouter完全指北 前言 关于标题,应该算不上是标题党,因为内容真的很多很长很全面.主要是在官网的基础上又详细总结,举例了很多东西.确保所有新人都能理解!所以实际上很多东西 ...

  4. Celery入门指北

    Celery入门指北 其实本文就是我看完Celery的官方文档指南的读书笔记.然后由于我的懒,只看完了那些入门指南,原文地址:First Steps with Celery,Next Steps,Us ...

  5. Laravel 集成 JPush 极光推送指北

    我是一个 Laravel 小白,我是一个 Laravel 小白,我是一个 Laravel 小白(默念三遍再往下读,如果非小白就不用看了). Laravel 使用 Composer 来管理代码依赖.所以 ...

  6. ThinkPHP 3.2.x 集成极光推送指北

    3.2版本已经过了维护生命周期,官方已经不再维护,请及时更新至5.0版本 -- ThinkPHP 官方仓库 以上,如果有条件,请关闭这个页面,然后升级至 ThinkPHP 5,如果由于各种各样的原因无 ...

  7. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  8. [转] iOS开发者的Weex伪最佳实践指北

    [From] http://www.cocoachina.com/ios/20170601/19404.html 引子 这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得,和大家一起分享分享 ...

  9. 微信小程序云开发不完全指北

    微信小程序云开发不完全指北 首先必须说明云开发的"云"并不是类似云玩家里的云的意思,而是微信小程序真的提供了云开发的接口以及一个简单的提供存储.数据库服务的虚拟后台(对于一些轻量小 ...

  10. msf stagers开发不完全指北(二)

    采用 Golang 开发stagers 上一篇文章 msf stagers开发不完全指北(一)中我们谈到如何采用 c 进行 msf 的 stagers 开发,这篇文章我们探讨一下如何使用 Golang ...

随机推荐

  1. 【Azure 事件中心】Event Hub 无法连接,出现 Did not observe any item or terminal signal within 60000ms in 'flatMapMany' 的错误消息

    问题描述 使用Java SDK连接Azure Event Hub,一直出现 java.util.concurrent.TimeoutException 异常, 消息为:java.util.concur ...

  2. go GMP

    动态栈 操作系统的线程一般都有固定的栈内存(通常为2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为2KB),所以在 Go 语言中一次创建 ...

  3. maple软件安装教程

    Maple2022适用于Win7/10/11(64位)系统,亲测可用! Maple2022 WIN10 64位安装步骤:1.先使用"百度网盘"下载MPE22_CN_x64安装包到电 ...

  4. UWSGI 安装出现 ModuleNotFoundError: No module named '_ctypes'

    原因:Python3中有个内置模块叫ctypes,它是Python3的外部函数库模块,它提供兼容C语言的数据类型,并通过它调用Linux系统下的共享库(Shared library),此模块需要使用C ...

  5. 研究光度立体法阶段性小结和优化(可20ms获取4个2500*2000灰度图的Normal Map)。

    这个东西是我接触的第一个非2D方面的算法,到目前为止其实也没有完全搞定,不过可能短时间内也无法突破.先把能搞定的搞定吧. 这个东西也有一大堆参考资料,不过呢,搜来搜去其实也就那些同样的东西,个人觉得就 ...

  6. windows环境变量修改器

    软件及源码 前言 我一直再用win7的系统,当更改path环境变量的时候很难受, 就只能看到一段,然后前面有啥后面有啥都看不到,而且来回调整优先级的时候需要剪切粘贴,主要就是来回调节优先级特别麻烦.所 ...

  7. 【Java EE】Day06 JDBC连接池介绍、C3P0连接池实现、Druid连接池实现、JDBCTemplate

    一.数据库连接池介绍 1.引入 之前:每次都要获取连接释放连接 现在:连接重复使用 2.概念: 存放数据库连接的容器 3.实现 DataSource接口 三种实现 标准实现 连接池实现 C3P0 Dr ...

  8. webflux延迟队列逻辑更改过程记录

    title : webflux延迟队列逻辑更改过程记录 author : simonLee date : 2022/11/22 10:26 目录 webflux延迟队列逻辑更改过程记录 一.问题背景 ...

  9. 瞧瞧别人家的API接口,那叫一个优雅

    前言 在实际工作中,我们需要经常跟第三方平台打交道,可能会对接第三方平台API接口,或者提供API接口给第三方平台调用. 那么问题来了,如果设计一个优雅的API接口,能够满足:安全性.可重复调用.稳定 ...

  10. Django测试脚本-单表操作(增删改查)-必知必会13条-神奇的双下划线

    目录 一:Django测试脚本 1.测试环境准备 2.tests.py 3.models.py 4.切换MySQL数据库 二:单表操作 1.pk关键字与get关键字 2.增 3.删 4.修 三:必知必 ...