用 PerformanceObserver 精准监控前端性能指标实战

百里玉浩 优化 阅读 1,377
赞 14 收藏
二维码
手机扫码查看
反馈

PerformanceObserver 报错?原来 entryTypes 是个坑

上周在搞一个性能监控模块,想用 PerformanceObserver 来收集页面的 LCP、FID 这些指标。结果一上线,控制台就报了一堆错:Invalid value for entryTypes: 'longtask'。我人傻了,这不就是官方文档里写的用法吗?怎么到我这儿就炸了?

用 PerformanceObserver 精准监控前端性能指标实战

折腾了半天才发现,不是我代码写错了,而是浏览器兼容性问题埋了个大雷。

一开始我以为是 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,防止极端环境崩溃

就这么简单,但没踩过坑的人很容易栽进去。我一开始也以为是个小问题,结果花了一下午才定位清楚,就是因为文档里没强调“不支持的类型会直接报错”这一点。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有更轻量的检测方式?或者在某些特殊环境下还有别的坑?我也想多学点。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论