Performance API不完全使用指北
本教程解释了如何使用Performance API来记录真实用户访问你的应用程序的统计数据。
使用浏览器的DevTools来评估web应用性能是很有用的,但要复现现实世界的使用情况并不容易。因为人们在不同地点使用不同的设备、浏览器和网络,都会有不同的体验。
Performance API介绍
Performance API使用一个缓冲区,在你的网页生命周期的确定节点上,在对象属性中记录类似DevTool的指标。这些节点包括:
- 页面导航:记录页面加载重定向、连接、握手、DOM事件等等。
- 资源加载:记录资源加载,比如图像、CSS、脚本以及Ajax调用。
- 绘制指标:记录浏览器渲染信息。
- 自定义:记录任意的应用处理时间,来找到运行慢的函数。
所有的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对象数组 |
注意:performanceServerTiming
的name
、description
和duration
等指标由服务器响应写入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 hooks(perf_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');
数组里的对象拥有entryType
、name
和startTime
属性:
[
{
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');
其他方法:
- .getEntries():返回所有性能条目的数组。
- .clearMarks([name]):清除指定名称的标记(不指定名称则清除所有标记)。
- .clearMeasures([name]):清除指定名称的测量(不指定名称则清除所有测量)。
PerformanceObserver可以监听缓冲区的更改,当指定对象出现时执行函数。观察者函数使用两个参数定义:
list
:观察者条目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
(一般来说是mark
,measure
或者resource
):
let observer = new PerformanceObserver(performanceHandler);
observer.observe({entryTypes: ['mark', 'measure']});
每当有新的标记或测量对象被推送到性能缓冲区,performanceHandler()
函数就会运行。
总结
Performance API提供了一种方法来测量网站和应用程序的速度,这些设备是由不同地点的人在一系列连接上使用的实际设备。它使每个人都能轻松地整理出类似DevTool的指标,并识别潜在的瓶颈。
以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~
Performance API不完全使用指北的更多相关文章
- 后端API入门到放弃指北
后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一 ...
- Python 简单入门指北(二)
Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...
- 可能比文档还详细--VueRouter完全指北
可能比文档还详细--VueRouter完全指北 前言 关于标题,应该算不上是标题党,因为内容真的很多很长很全面.主要是在官网的基础上又详细总结,举例了很多东西.确保所有新人都能理解!所以实际上很多东西 ...
- Celery入门指北
Celery入门指北 其实本文就是我看完Celery的官方文档指南的读书笔记.然后由于我的懒,只看完了那些入门指南,原文地址:First Steps with Celery,Next Steps,Us ...
- Laravel 集成 JPush 极光推送指北
我是一个 Laravel 小白,我是一个 Laravel 小白,我是一个 Laravel 小白(默念三遍再往下读,如果非小白就不用看了). Laravel 使用 Composer 来管理代码依赖.所以 ...
- ThinkPHP 3.2.x 集成极光推送指北
3.2版本已经过了维护生命周期,官方已经不再维护,请及时更新至5.0版本 -- ThinkPHP 官方仓库 以上,如果有条件,请关闭这个页面,然后升级至 ThinkPHP 5,如果由于各种各样的原因无 ...
- c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)
c#封装DBHelper类 public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...
- [转] iOS开发者的Weex伪最佳实践指北
[From] http://www.cocoachina.com/ios/20170601/19404.html 引子 这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得,和大家一起分享分享 ...
- 微信小程序云开发不完全指北
微信小程序云开发不完全指北 首先必须说明云开发的"云"并不是类似云玩家里的云的意思,而是微信小程序真的提供了云开发的接口以及一个简单的提供存储.数据库服务的虚拟后台(对于一些轻量小 ...
- msf stagers开发不完全指北(二)
采用 Golang 开发stagers 上一篇文章 msf stagers开发不完全指北(一)中我们谈到如何采用 c 进行 msf 的 stagers 开发,这篇文章我们探讨一下如何使用 Golang ...
随机推荐
- Fidder 抓包工具
fiddler抓包原理 如上图本文一些 不重要 的鸡肋功能 自行百度 1. 安装与配置 1. 安装 安装地址https://www.telerik.com/download/fiddler可能有点慢 ...
- Java安全之动态加载字节码
Java字节码 简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令. 由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需要将自 ...
- Go语言核心36讲05
你已经使用过Go语言编写了小命令(或者说微型程序)吗? 当你在编写"Hello, world"的时候,一个源码文件就足够了,虽然这种小玩意儿没什么用,最多能给你一点点莫名的成就感. ...
- 【OpenStack云平台】Packmaker 集群
个人名片: 因为云计算成为了监控工程师 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying Packmaker 集群 1.1 安装软件包 1.2 Corosync 基本配置 1.3 启 ...
- 简单的sql注入3
仍然 1 1' 1" 发现1'报错了.....我觉得作者对'情有独钟 再试试 1# 1'# 1"# 发现都可以正常登录 试试1' and '1'='1和1' and '1'='2发 ...
- 1 c++编程基础
重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦! 生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 1 c++揭开面纱 1.1 编程 ...
- Vue2组件间通讯
Vue2组件通信的基础方式 自己的理解:组件化通信,无非就是数据你传我,我传你,两个组件的相互交流,方法很多,下方有图示(此篇建议小白阅读,大神的话也不会看,哈哈哈哈!仅供参考,有不同的意见可以一起交 ...
- tostring、(string)和 String.valueOf()
上周遇到一个问题,只怪自己平时没注意这个细节,从数据库取数据在map集合里,取出该值是我用了.tostring的方法,一次在当取出数据为空时代码报java.lang.NullPointerExcept ...
- 【每日一题】【使用list&使用辅助栈实现】2022年2月11日-NC90 包含min函数的栈
描述定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop.top 和 min 函数操作时,栈中一定有元素. 此栈包含的方法有:push(value): ...
- Java中的反射机制及反射的优缺点
1. 反射的概念 反射 机制指的是,程序在运行时能够获取自身的信息.在 java 中只要给定类的名字,就能够获取类的所有属性和方法. 反射是 Java 中很多高级特性的基础,比如 注解.动态代理 以及 ...