PerformanceObserver
是 Web Performance API 的一部分,它提供了一种高效且非侵入式的方式来收集各种性能指标。与传统的 performance.getEntries()
方法不同,PerformanceObserver
允许你异步地观察性能事件,并在它们发生时得到通知,而不是在某个特定时间点去查询所有已发生的事件。
什么是 PerformanceObserver
?
PerformanceObserver
是一个 JavaScript 接口,它允许你订阅特定类型的性能事件。当这些事件发生时,它会通过一个回调函数异步地通知你。这使得你可以实时地监控页面性能,而不会阻塞主线程或频繁地查询性能数据。
主要优势:
- 非阻塞和高效:它不会阻塞主线程,并且只在有新性能条目可用时才触发回调,避免了轮询的开销。
- 实时监控:能够捕获到页面生命周期中动态发生的性能事件,例如资源加载、长任务、布局偏移等。
- 更全面的数据 :可以观察到多种类型的性能条目,包括用户自定义的计时(
mark
,measure
)、资源加载(resource
)、导航(navigation
)、渲染(paint
)、长任务(longtask
)等。 - 支持
buffered
选项:可以获取在观察器创建之前就已经发生的性能条目。
如何使用 PerformanceObserver
?
使用 PerformanceObserver
主要分为三个步骤:
- 创建
PerformanceObserver
实例:传入一个回调函数,当有新的性能条目时,这个回调函数会被调用。 - 指定要观察的性能条目类型 :使用
observe()
方法,传入一个配置对象,其中包含entryTypes
数组。 - 处理性能数据 :在回调函数中访问
PerformanceObserverEntryList
对象,它包含了所有新的性能条目。
基本结构
js
// 1. 创建 PerformanceObserver 实例
const observer = new PerformanceObserver((entryList, observer) => {
// 2. 回调函数,当有新的性能条目时会被调用
const entries = entryList.getEntries();
entries.forEach(entry => {
// 处理每个性能条目
console.log(`Entry Type: ${entry.entryType}`);
console.log(`Name: ${entry.name}`);
console.log(`Start Time: ${entry.startTime}`);
console.log(`Duration: ${entry.duration}`);
// 根据 entry.entryType 可能会有更多属性
});
// 可选:在某些条件下断开观察器,以停止接收通知
// observer.disconnect();
});
// 3. 指定要观察的性能条目类型
// 例如,观察 'paint' (首次绘制, 首次内容绘制) 和 'resource' (资源加载) 事件
observer.observe({
entryTypes: ['paint', 'resource', 'navigation', 'longtask', 'mark', 'measure', 'element', 'layout-shift'],
buffered: true // 收集在 observe() 调用之前发生的条目
});
// 示例:手动触发一些性能条目
performance.mark('start_app');
// 模拟一些耗时操作
for (let i = 0; i < 1000000; i++) {
Math.random();
}
performance.mark('end_app');
performance.measure('app_load_time', 'start_app', 'end_app');
console.log("PerformanceObserver 已启动,请查看控制台输出。");
PerformanceObserver
的回调函数参数
entryList
(PerformanceObserverEntryList
):一个对象,包含了所有新观察到的性能条目。你可以使用getEntries()
,getEntriesByName()
,getEntriesByType()
等方法来获取条目。observer
(PerformanceObserver
):对当前PerformanceObserver
实例的引用,可以在回调函数内部调用disconnect()
方法。
observe()
方法的选项
entryTypes
(Array):一个字符串数组,指定要观察的性能条目类型。buffered
(boolean, 可选):如果设置为true
,观察器将包含在observe()
调用之前已经发生的匹配类型的性能条目。这对于捕获页面加载初期发生的事件(如navigation
,paint
)非常有用。
常见的 entryTypes
类型
以下是一些常用的 entryTypes
及其用途:
-
'navigation'
:- 用途:获取页面导航和加载时间信息,例如 DNS 查询、TCP 连接、DOM 解析等。对应 Navigation Timing API。
- 示例:计算页面完全加载时间。
- 属性 :
domContentLoadedEventEnd
,loadEventEnd
,responseStart
等。
-
'resource'
:- 用途:获取页面加载的各种资源(图片、CSS、JS、XHR 请求等)的详细加载时间。对应 Resource Timing API。
- 示例:分析哪些资源加载缓慢。
- 属性 :
initiatorType
,nextHopProtocol
,duration
,transferSize
等。
-
'paint'
:- 用途 :用于测量关键渲染指标,如
first-paint
(FP) 和first-contentful-paint
(FCP)。 - 示例:监控用户何时开始看到页面内容。
- 属性 :
name
(例如'first-paint'
,'first-contentful-paint'
)。
- 用途 :用于测量关键渲染指标,如
-
'longtask'
:- 用途:检测在主线程上执行时间过长的任务(通常是 50 毫秒或更长),这些任务可能导致页面卡顿。
- 示例:识别导致 UI 无响应的 JavaScript 执行。
- 属性 :
name
(总是'longtask'
),duration
,startTime
,attribution
(提供任务来源信息)。
-
'mark'
和'measure'
:- 用途:通过 User Timing API 允许开发者在代码中自定义性能标记和测量时间间隔。
- 示例:测量特定函数或代码块的执行时间。
performance.mark(name)
:在时间线上创建一个标记。performance.measure(name, startMark, endMark)
:测量两个标记之间的时间。
-
'element'
:- 用途:用于测量特定 DOM 元素的渲染时间,特别是用于计算 Largest Contentful Paint (LCP)。
- 示例:监控页面上最大内容元素的加载和渲染时间。
- 属性 :
renderTime
,loadTime
,element
(对 DOM 元素的引用)。
-
'layout-shift'
:- 用途:用于测量 Cumulative Layout Shift (CLS),即页面内容在视觉上发生偏移的累积分数。
- 示例:识别导致页面抖动的布局变化。
- 属性 :
value
(布局偏移分数),hadRecentInput
(是否由用户输入引起)。
-
'event'
:- 用途:测量用户交互事件(如点击、键盘输入)从发生到浏览器处理完成的延迟。
- 示例:分析输入延迟(Interaction to Next Paint - INP)。
- 属性 :
processingStart
,processingEnd
,duration
等。
实际应用示例
示例 1: 测量 FCP (First Contentful Paint)
js
const fcpObserver = new PerformanceObserver((entryList, observer) => {
for (const entry of entryList.getEntriesByName('first-contentful-paint')) {
console.log('FCP:', entry.startTime);
// FCP 通常只需要测量一次,所以一旦获取到就可以断开观察器
observer.disconnect();
}
});
fcpObserver.observe({ type: 'paint', buffered: true });
示例 2: 检测长任务 (Long Tasks)
js
const longTaskObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.warn(`检测到长任务: ${entry.name},持续时间: ${entry.duration.toFixed(2)}ms`);
if (entry.attribution) {
console.warn('长任务归因:', entry.attribution);
}
}
});
longTaskObserver.observe({ type: 'longtask', buffered: true });
// 模拟一个长任务
function simulateLongTask() {
let sum = 0;
for (let i = 0; i < 500000000; i++) { // 这是一个非常长的循环
sum += i;
}
console.log("模拟长任务完成:", sum);
}
// 在某个时机调用它,例如用户点击后
document.addEventListener('click', () => {
console.log("点击事件触发,即将执行模拟长任务...");
simulateLongTask();
});
示例 3: 测量自定义代码块的性能
js
// 在代码的关键点打上标记
performance.mark('myFunctionStart');
function myFunction() {
// 模拟一些工作
let result = 0;
for (let i = 0; i < 100000000; i++) {
result += Math.sqrt(i);
}
return result;
}
const res = myFunction();
// 在代码结束点打上标记
performance.mark('myFunctionEnd');
// 测量两个标记之间的时间
performance.measure('myFunctionDuration', 'myFunctionStart', 'myFunctionEnd');
const customMeasureObserver = new PerformanceObserver((entryList, observer) => {
for (const entry of entryList.getEntriesByType('measure')) {
console.log(`自定义测量: ${entry.name},持续时间: ${entry.duration.toFixed(2)}ms`);
// 假设只测量一次
observer.disconnect();
}
});
customMeasureObserver.observe({ entryTypes: ['measure'] });
性能考量和注意事项
disconnect()
:当你不再需要观察特定类型的性能事件时,务必调用observer.disconnect()
来停止观察器,释放资源,避免不必要的开销。对于只需要获取一次的指标(如 FCP),在获取到数据后立即断开。- 回调频率 :
PerformanceObserver
的回调函数可能会在短时间内被多次调用,尤其是在页面加载过程中或有大量长任务发生时。确保你的回调函数执行效率高,避免在其中执行复杂的同步操作。 - 数据量 :某些
entryTypes
(如resource
)可能会产生大量的性能条目。在处理这些数据时,要注意内存和 CPU 消耗。 - 浏览器兼容性 :虽然
PerformanceObserver
现代浏览器支持良好,但在旧版浏览器中可能需要 Polyfill 或降级处理。 buffered: true
的使用:这个选项非常有用,但也要注意它会返回所有历史条目。如果你只需要新发生的条目,可以省略它。
PerformanceObserver
是现代 Web 性能监控的基石,它使得开发者能够以一种强大而高效的方式收集和分析用户体验的关键指标。