一 概述
1. 什么是 Performance
首先 Performance
是一个标准,用于解决开发者在浏览器中获取性能数据的问题。其次, Performance
是一个浏览器全局对象,提供了一组 API 用于编程式地获取程序在某些节点的性能数据。它包含一组高精度时间定义,以及配套的相关方法。
2. 新的 Performance 标准
新的 Performance
标准包含了许多 API,它们各自针对性能监控的某个方面。但是目前 Performance
部分标准处于实验阶段,并未稳定发布,且已发布的标准,各浏览器支持度差异较大。
API | 描述 | 状态 | 兼容性 |
---|---|---|---|
Navigation Timing | 导航计时。用于记录并检测用户的设备,网络等环境,以及页面初始资源加载和解析耗时。 | 稳定 | >= IE 9 |
High Resolution Timing | 精确计时。用于精确到微秒级别的计时,实际使用时精度受浏览器实现限制,通常精确到毫秒 | 稳定 | >= IE 10 |
Resource Timing | 资源计时。对单个网络资源的计时,包含页面从打开到获取时的所有网络资源。 | 稳定 | >= IE 10, Safari 不支持 |
Page Visiblity | 页面可见性。获取页面是否可见(例如用户切换了标签页,当前页面隐藏,此时为不可见) | 稳定 | >= IE 10 |
Battery Status | 电池状态。获取当前设备是否充电,电量等级等。对移动设备来说非常实用。 | 稳定 | IE, Safari不支持, Android WebView >=40 |
User Timing | 用户计时。手动对某段代码或函数计时。 | 稳定 | >= IE 10, Firfox 40, safari 11 |
Beacon | 灯塔。用于将分析结果和诊断数据采用非阻塞异步的方式发送到服务器,Beacon 方法保证在页面关闭前执行。 | 实验 | IE, Safari, IOS, Android 不支持 |
Frame Timing | 帧计时。获取与帧相关的性能数据,例如每秒帧数 | 实验 | 不支持 |
3. 旧的 Performance 标准
PerformanceTiming
接口是为了向下兼容而存在的接口。它包含了全部的 Navigation Timing
以及部分 Resource Timing
的数据。PerformanceTiming
对象的兼容性是最好的,通过这个接口可以获取到页面从点击链接到页面渲染完毕的各个关键时间节点。
4. 如何获取 Performance
通过 window.performance
来获取 Performance
对象。
window.performance
// Performance {
// timeOrigin: 1611749316627.347,
// onresourcetimingbufferfull: null,
// eventCounts: EventCounts,
// timing: PerformanceTiming,
// navigation: PerformanceNavigation,
// ...
// }
PerformanceEntry
接口可以分类获取 performance 运行时的实例数据,例如手动创建的 mark
标记产生的性能数据,或者是在异步资源加载时(css,script,ajax,等)被动创建的资源标记
二 PerformanceTiming
1. 概述
PerformanceTiming
接口提供了在页面加载和使用时的各种性能计时信息。通过 window.performance.timing
获得一个实例对象。
2. 属性
PerformanceTiming 接口的所有属性都是只读的,且没有任何继承属性,由于所有属性均是某个时间点,因此值类型都是 无符号 long long 类型(double)
(1) PerformanceTiming.navigationStart
PerformanceTiming.navigationStart
表示同一个浏览器上下文中,上一个文档卸载结束的 UNIX 时间戳。如果没有上一个文档,这个值与 PerformanceTiming.fetchStart
相同。
(2) PerformanceTiming.unloadEventStart
PerformanceTiming.unloadEventStart
表示 unload
事件抛出时的 UNIX 时间戳。如果没有上一个文档,或者重定向中的一个与当前文档不同源,该值为 0
。
(3) PerformanceTiming.unloadEventEnd
PerformanceTiming.unloadEventEnd
表示 unload
事件处理完成时的 UNIX 时间戳。如果没有上一个文档,或者重定向中的一个与当前文档不同源,该值为 0
。
(4) PerformanceTiming.redirectStart
PerformanceTiming.redirectStart
表示第一个 HTTP 重定向开始时的 UNIX 时间戳。如果没有重定向,或者重定向中的一个不同源,该值为 0
。
(5) PerformanceTiming.redirectEnd
PerformanceTiming.redirectEnd
表示最后一个 HTTP 重定向完成时(即最后一个 HTTP 响应的最后一个比特被接收到的时间)的 UNIT 时间戳。如果额米有重定向,或者重定向中的一个不同源,该值为 0
。
(6) PerformanceTiming.fetchStart
PerformanceTiming.fetchStart
表示浏览器准备好用 HTTP 请求来获取文档的 UNIX 时间戳。这个时间早于检查应用缓存。
(7) PerformanceTiming.domainLookupStart
PerformanceTiming.domainLookupStart
表示域名查询开始的 UNIX 时间戳。如果使用了持续连接,或者这个信息被存储到了缓存或本地资源,那么该值与 PerformanceTiming.fetchStart
相同。
(8) PerformanceTiming.domainLookupEnd
PerformanceTiming.domainLookupEnd
表示域名查询结束的 UNIX 时间戳。如果使用了持续连接,或者这个信息被存储到了缓存或本地资源,那么该值与 PerformanceTiming.fetchStart
相同。
(9) PerformanceTiming.connectStart
PerformanceTiming.connectStart
表示 HTTP 请求开始向服务器发送时的 UNIX 时间戳。如果使用持久连接,则该值与 PerformanceTiming.fetchStart
相同。
(10) PerformanceTiming.connectEnd
PerformanceTiming.connectEnd
表示浏览器与服务器之间的连接建立(即握手与认证等过程全部结束)的 UNIX 时间戳。如果使用持久连接,则该值与 PerformanceTiming.fetchStart
相同。
(11) PerformanceTiming.secureConnectionStart
PerformanceTiming.secureConnectionStart
表示浏览器与服务器开始安全链接的握手时的 UNIX 时间戳。如果当前网页不要求安全链接,该值为 0
。
(12) PerformanceTiming.requestStart
PerformanceTiming.requestStart
表示浏览器向服务器发送 HTTP 请求时的 UNIX 时间戳。
(13) PerformanceTiming.responseStart
PerformanceTiming.responseStart
表示浏览器从服务器收到(或从本地缓存读取)第一个字节时的 UNIX 时间戳。如果传输层从开始请求后失败并连接被重开,该值会被重置为新的请求的相应的时间。
(14) PerformanceTiming.responseEnd
PerformanceTiming.responseEnd
表示浏览器从服务器收到(或从本地缓存读取,或从本地资源读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭的时间)的 UNIX 时间戳。
(15) PerformanceTiming.domLoading
Performance.domLoading
表示当前网页 DOM
结构开始解析时(即 Document.readyState
属性变为 loading
,相应的 readystatechange
事件触发时)的 UNIX 时间戳。
(16) PerformanceTiming.domInteractive
Performance.domInteractive
表示当前网页 DOM
结构解析结束,开始加载内嵌资源时(即 Document.readyState
的属性为 interactive
,相应的 readystatechange
事件触发时)的 UNIX 时间戳。
(17) PerformanceTiming.domContentLoadedEventStart
PerformanceTiming.domContentLoadedEventStart
表示解析器触发 DomContentLoaded
事件,即所有需要被执行的脚本已经被解析时的 UNIX 时间戳。
(18) PerformanceTiming.domContentLoadedEventEnd
PerformanceTiming.domContentLoadedEventEnd
表示所有需要被执行的脚本均已被执行完成时的 UNIX 时间戳。
(19) PerformanceTiming.domComplete
PerformanceTiming.domComplete
表示文档解析完成,即 Document.readyState 变为 complete
且相应的 readystatechange
事件被触发时的 UNIX 时间戳。
(20) PerformanceTiming.loadEventStart
PerformanceTiming.loadEventStart
表示该文档下,load
事件被触发的 UNIX 时间戳。如果还未发送,值为 0
。
(21) PerformanceTiming.loadEventEnd
PerformanceTiming.loadEventEnd
表示该文档下,load
事件结束,即加载事件完成时的 UNIX 时间戳,如果事件未触发或未完成,值为 0
。
3. 各个性能时间节点
先看一张社区流传甚广的图片:
这张图描述了从用户开始路由到这个页面,到这个页面完全加载完成,总过经历的所有过程,根据图片,我们可以划分出各个有意义的考察性能的时间节点:
时间段 | 描述 |
---|---|
navigationStart ~ unloadEventEnd | 上一页面的卸载耗时 |
fetchStart ~ domainLookupStart | 查询 app DNS 缓存耗时 |
domainLookupStart ~ domainLookupEnd | dns 查询耗时 |
connectStart ~ connectEnd | TCP 连接耗时 |
connectEnd ~ secureConnectionStart | 针对 https 协议,在 tcp 握手后,传输真正的内容前,建立安全连接的耗时 |
fetchStart ~ responseStart | TTFB (time to first byte), 即首包时长(从用户代理发出第一个请求开始,到页面读取到第一个字节的耗时)。在一个 web 程序中,用户代理发送的第一个 get 请求一般是 index.html,即接收到这个 html 文件的第一个字节的耗费时间 |
responseStart ~ responseEnd | 内容响应时长 |
domLoading ~ domInteractive | dom 解析完成,即 DOM 树构建完成的时长 |
domContentLoadedEventEnd ~ loadEventStart | 渲染时长,domContentLoaded 表示 DOM ,CSSOM 均准备就绪(CSSOM 就绪意味着没有样式表阻止 js 脚本执行),开始构建渲染树 |
navigationStart ~ domLoading | FPT (first paint time), 即首次渲染时间,或白屏时间,从用户打开页面开始,到第一次渲染出可见元素为止 |
navigationStart ~ domInteractive | TTI (time to interact),首次可交互时间 |
fetchStart ~ domContentLoadedEventEnd | html 加载完成时间,此时 DOM 已经解析完成 |
navigationStart ~ loadEventStart | 页面完全加载完成的时间 |
三 PerformanceEntry
PerformanceTiming
是向下兼容的旧接口,且只能保存页面打开过程(从路由到该地址,到当前页面load事件触发完成)中的性能数据,如果需要在页面运行过程中持续获取性能数据,需要使用 PerformanceEntry
实例。
1. Performance.getEntries
Performance.getEntries([filterOption])
方法用于获取 PerformanceEntry
实例数组,它接受一个可选的参数,如果不传则获取全部的 entry
数据。该参数对象可以包含以下,属性:
name
: performance entry 的名字entryType
: entry 类型,合法的 entry 值可以从 PerformanceEntry.entryTypeinitiatorType
: 初始化资源的类型,取值由PerformanceResourceTiming.initiatorType
接口定义
该方法返回 entries
数组,包含了所有符合筛选条件的 entry
。
window.performance.getEntries({ entryType: 'resource' })
/*
0: PerformanceNavigationTiming {unloadEventStart: 0, unloadEventEnd: 0, domInteractive: 1988.2200000574812, domContentLoadedEventStart: 2038.9849999919534, domContentLoadedEventEnd: 2049.5600000722334, …}
1: PerformanceResourceTiming {initiatorType: "link", nextHopProtocol: "h2", workerStart: 0, redirectStart: 0, redirectEnd: 0, …}
...
13: PerformancePaintTiming {name: "first-paint", entryType: "paint", startTime: 1987.154999980703, duration: 0}
*/
2. Performance.getEntriesByName
Performance.getEntriesByName(name [, type])
方法接受两个参数:
name
: 必选,这个 entry 的名称type
: 可选,想要过滤的 entry 类型
返回符合条件的 entries
数组。
window.performance.mark('_my_mark_1')
window.performance.getEntriesByName('_my_mark_1')
// [PerformanceMark]
// 0: PerformanceMark {detail: null, name: "_my_mark_1", entryType: "mark", startTime: 5614367.25000001, duration: 0}
3. Performance.getEntriesByType
Performance.getEntriesByType(type)
方法接受一个 entryType
,返回符合类型的 PerformanceEntry
列表。
window.performance.getEntriesByType('paint')
/*
[PerformancePaintTiming, PerformancePaintTiming]
0: PerformancePaintTiming {name: "first-paint", entryType: "paint", startTime: 2775.984999956563, duration: 0}
1: PerformancePaintTiming {name: "first-contentful-paint", entryType: "paint", startTime: 2775.984999956563, duration: 0}
*/
4. PerformanceEntry 属性
PerformanceEntry 的所有属性都是只读的
(1) PerformanceEntry.entryType,PerformanceEntry.name
PerformanceEntry.entryType
是一个 performance entry 的类型,值为 DOMString
。用于区分不同类型的 performance 数据。PerformanceEntry.name
标识当前性能数据块的名称,它允许的值和类型随着 entryType
不同而不同
值 | 标识的性能时间类型 | name 属性类型 | name 属性含义 |
---|---|---|---|
frame , navigation | PerformanceFrameTiming , PerformanceNavigationTiming | URL | 产生这两个performance数据的文档地址 |
resource | PerformanceResourceTiming | URL | 请求的资源的地址,即便请求被重定向了,这个值也不会改变 |
mark | PerformanceMark | DOMString | 创建该 mark 时指定的 name 属性 |
measure | PerformanceMeasure | DOMString | 使用 performance.measure() 创建 measure 时提供的 name 属性 |
paint | PerformancePaintTiming | DOMString | first-paint 或者 first-contentful-paint |
(2) PeformanceEntry.startTime
performanceEntry.startTime
表示这个 performance 数据被创建时的时间戳。根据不同的 entryType
值,startTime
取值的含义有些不同:
frame
: 指定的帧(frame
)开始渲染时的时间mark
: 该mark
标记被创建时的时间戳(performance.mark()
调用时的时间)measure
: 该 measure 被创建时的时间戳(performance.measure()
方法调用时的时间)navigation
: 值为 0resource
: 浏览器发起该资源请求时的时间戳
(3) PerformanceEntry.duration
performanceEntry.duration
表示这个 performance 数据标识的持续时间。根据不同的 entryType
值,duration
取值的含义有些不同:
frame
: 两个连续的帧开始渲染的时间之差mark
: 值为 0,mark
标记没有持续时间measure
: 要测量的 measure 的持续时间navigation
:performanceEntry.startTime
和performanceNavigationTiming.loadEventEnd
的差值resource
: 该资源加载的耗时,即performanceEntry.startTime
和performanceResourseTiming.responseEnd
的差值
四 手动测量性能
Performance
接口除了测量资源加载性能(PerformanceResourceTiming),渲染帧性能(PerformanceFrameTiming),页面初始化性能(PerformanceNavigationTiming)等等以外,还允许手动创建标记,让开发者在代码中测量程序执行的性能。
1. Performance.mark
performance.mark(name)
方法接受一个字符串,用于创建一个标记(mark),字符串即为这个标记的名称,这个标记会固定下创建时的时间戳。后续开发者可以通过不同标记的 startTime 值相减来获取一段代码的执行耗时,也可以通过 performance.measure 来计算耗时。该方法返回一个 mark
类型的 PerformanceEntry
实例。
一个标记(mark)具有以下属性值:
entryType
: 固定为mark
name
: 创建标记时通过参数指定的标记名startTime
: 创建mark
时的时间戳duration
: 0,mark
没有持续时间
window.performance.mark('_test_mark_1')
// PerformanceMark {detail: null, name: "_test_mark_1", entryType: "mark", startTime: 1978026.354999999, duration: 0}
window.performance.mark('_test_mark_2')
// PerformanceMark {detail: null, name: "_test_mark_2", entryType: "mark", startTime: 1991866.0800000008, duration: 0}
window.performance.mark('_test_mark_1')
// PerformanceMark {detail: null, name: "_test_mark_1", entryType: "mark", startTime: 1994302.8449999993, duration: 0}
performance.getEntriesByName('_test_mark_1')
// [PerformanceMark, PerformanceMark]
mark 的 name 不要求唯一
2. Performance.measure
performance.measure(name [, startMarkName [, endMarkName]])
方法创建一个 entryType 类型为 measure 的 PerformanceEntry 对象,用于计算两个标志位之间的间隔时间。它接受三个参数:
name
: 创建 measure 时指定的名称startMarkName
: 可选,指定作为开始时间点的 mark 的名称endMarkName
: 可选,指定作为结束时间点的 mark 的名称
该方法返回一个 measure
类型的 PerformanceEntry
,它具有以下属性
entryType
: 值为measure
name
: 创建measure
时的名称startTime
: 这一段测量的开始时间,如果传了第二个参数,那就是该 mark 的标记时间duration
: 这一段测量持续的时间
window.performance.measure('__measure_test_mark_1', '_test_mark_1')
// PerformanceMeasure {detail: null, name: "__measure_test_mark_1", entryType: "measure", startTime: 1994302.8449999993, duration: 476087.44500000123}
window.performance.measure('__measure_test_mark', '_test_mark_1', '_test_mark_2')
PerformanceMeasure {detail: null, name: "__measure_test_mark", entryType: "measure", startTime: 1994302.8449999993, duration: -2436.7649999985006}
window.performance.measure('__measure_test_mark')
PerformanceMeasure {detail: null, name: "__measure_test_mark", entryType: "measure", startTime: 0, duration: 3290800.8249999993}
综上, duration
取值有三种情况
- 调用 performance.measure 创建 measure 时,三个参数都传递了,即 name, startMark, endMark 同时存在,那么 duration = startMark.startTime - endMark.startTime (注意,并不是 endMark - startMark),duration 此时可能是负值
- 创建 measure 只传了两个参数,即 endMark 不存在,那么该 measure 计算的是从 startMark.startTime 到创建 measure 时的时间间隔
- 创建 measure 只提供了一个参数,即 startMark, endMark 都不存在,那么该 measure 计算的是从页面打开到创建 measure 时的时间间隔
3. 清理 mark,measure
Performance.clearMark([name])
方法用于清理指定的mark
,如果不提供参数,则所有 entryType 为mark
的PerformanceEntry
全部被清理。Performance.clearMeasure([name])
方法用于清理指定的measure
,如果不提供参数,则所有 entryType 为measure
的PerformanceEntry
全部被清理。
performance.clearMarks('_test_mark_1')
undefined
performance.getEntriesByType('mark')
// [PerformanceMark, PerformanceMark]
// PerformanceMark {detail: null, name: "LUX_end", entryType: "mark", startTime: 2039.1650000001391, duration: 0}
// PerformanceMark {detail: null, name: "_test_mark_2", entryType: "mark", startTime: 1991866.0800000008, duration: 0}