用 PerformanceObserver 精准监控前端性能指标实战
PerformanceObserver 报错?原来 entryTypes 是个坑
上周在搞一个性能监控模块,想用 PerformanceObserver 来收集页面的 LCP、FID 这些指标。结果一上线,控制台就报了一堆错:Invalid value for entryTypes: 'longtask'。我人傻了,这不就是官方文档里写的用法吗?怎么到我这儿就炸了?
折腾了半天才发现,不是我代码写错了,而是浏览器兼容性问题埋了个大雷。
一开始我以为是 API 用错了
我最开始的代码大概是这样的:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
}
});
observer.observe({ entryTypes: ['longtask', 'largest-contentful-paint'] });
看起来没问题对吧?MDN 上也是这么写的。但一跑起来,Chrome 90+ 没事,Safari 15.4 以下直接报错,连 observer 都没注册成功。更离谱的是,有些安卓 WebView 根本不支持 longtask,但又不会提前告诉你,直接抛异常,整个监控脚本就挂了。
我一开始还以为是自己拼写错了,反复检查了 entryTypes 的值,确认大小写、单复数都没问题。后来甚至怀疑是不是打包工具把字符串压缩坏了……结果都不是。
踩坑:不能一股脑全塞进去
关键点来了:PerformanceObserver 的 observe 方法要求传入的 entryTypes 数组里的每一项,都必须是当前浏览器支持的类型。如果你传了一个不支持的类型(比如老版本 Safari 不支持 longtask),它就会直接 throw error,而不是忽略不支持的项。
这设计真有点反直觉。我以为它会像 IntersectionObserver 那样,不支持的配置项就默默忽略,结果它直接给你掀桌子。
所以问题不是“怎么用”,而是“怎么安全地用”——得先判断浏览器支不支持某个 entryType,再决定要不要加进去。
解决方案:动态过滤 entryTypes
我后来试了下,发现可以通过 PerformanceObserver.supportedEntryTypes 这个静态属性来获取当前浏览器支持的所有类型。这个属性是规范里定义的,主流现代浏览器基本都支持了(包括 Safari 15.4+)。
于是我把代码改成这样:
function safeObserve(entryTypes, callback) {
// 先获取浏览器支持的类型
const supported = PerformanceObserver.supportedEntryTypes || [];
// 只保留支持的类型
const filteredTypes = entryTypes.filter(type => supported.includes(type));
if (filteredTypes.length === 0) {
console.warn('No supported entryTypes to observe');
return;
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
callback(entry);
}
});
try {
observer.observe({ entryTypes: filteredTypes });
} catch (err) {
console.error('Failed to observe performance entries:', err);
}
}
// 使用
safeObserve(
['longtask', 'largest-contentful-paint', 'first-input', 'layout-shift'],
(entry) => {
// 上报逻辑
fetch('https://jztheme.com/api/perf', {
method: 'POST',
body: JSON.stringify({ type: entry.entryType, value: entry.startTime }),
});
}
);
这段代码的核心就是:先查 supportedEntryTypes,再过滤掉不支持的类型,最后才调用 observe。这样即使浏览器不支持 longtask,也不会报错,至少还能收集到 LCP、CLS 这些基础指标。
这里我踩了个小坑:一开始忘了加 try...catch,结果在极少数老旧环境(比如某些定制安卓浏览器)里,PerformanceObserver 对象存在,但 supportedEntryTypes 是 undefined,或者 observe 本身还有其他限制,导致还是会崩溃。加上 try-catch 后,至少不会影响主流程。
补充:有些浏览器支持但返回空数组
还有一个细节:在某些浏览器(比如旧版 Edge)中,PerformanceObserver.supportedEntryTypes 存在,但返回的是空数组 []。这时候你过滤完 filteredTypes 就是空的,直接 return 掉,避免无效调用。
虽然这种情况不多,但加上 if (filteredTypes.length === 0) 判断后,日志里就不会出现“试图观察空类型”的奇怪行为,调试起来也清爽点。
最终效果:稳定上报,不再崩
改完之后,线上错误率直接降为 0。虽然在低端机上可能收不到 longtask 数据,但至少 LCP、FID、CLS 这些核心指标都能正常采集,不影响整体性能分析。
其实严格来说,这方案也不是 100% 完美——比如某些浏览器可能支持 longtask,但需要用户手势触发(比如 iOS 的某些限制),这时候你注册了也收不到数据。但至少不会因为 API 报错导致整个监控模块失效,这就够了。
毕竟前端性能监控本来就是“能收多少收多少”,没必要强求所有指标在所有设备上都完美上报。
顺便说一句:别忘了 polyfill?
有人可能会问:要不要加 polyfill?我的建议是别加。PerformanceObserver 是底层 API,polyfill 很难模拟真实性能数据(比如你没法凭空造出一个真实的 LCP 时间)。而且这类 API 通常只在现代浏览器才有意义,老浏览器本身就该被降级处理。
所以我的策略一直是:用能力检测 + 优雅降级,而不是硬上 polyfill。
核心代码就这几行,但坑不少
总结一下,关键就三点:
- 永远不要直接把一堆 entryTypes 传给
observe - 先用
PerformanceObserver.supportedEntryTypes过滤 - 包一层 try-catch,防止极端环境崩溃
就这么简单,但没踩过坑的人很容易栽进去。我一开始也以为是个小问题,结果花了一下午才定位清楚,就是因为文档里没强调“不支持的类型会直接报错”这一点。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有更轻量的检测方式?或者在某些特殊环境下还有别的坑?我也想多学点。

暂无评论