🎯 Web 性能 API 集合:Performance Observer 的 5 个冷门妙用

性能监控|浏览器 API | 你以为 Performance API 只是 performance.now()?PerformanceObserver 能在不阻塞主线程的前提下监听到 LCP、FMP、长任务、内存泄漏......甚至能捕获「谁在偷偷做耗时的同步操作」。

问题场景

线上项目偶尔出现卡顿,但在 DevTools 里抓不到复现。打点日志也看不出端倪------因为你只能看到结果 (页面慢了),看不到原因(谁占用了主线程)。

js 复制代码
// 传统的打点方式
const start = performance.now()
doHeavyWork()
console.log(`耗时:${performance.now() - start}ms`)

这种方式有两个致命缺陷:

  1. 侵入性强------要改造业务代码
  2. 抓不到偶发问题------你不知道什么时候该打点

解决方案:PerformanceObserver

PerformanceObserver 是一个观察者模式的性能 API,可以订阅多种性能事件,在事件发生时异步回调,零侵入。

1️⃣ 捕获所有长任务(Long Tasks)

哪些函数霸占了主线程超过 50ms?直接抓:

js 复制代码
const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    console.warn(`⚠️ 长任务 ${entry.duration.toFixed(0)}ms`, {
      name: entry.name,
      startTime: entry.startTime,
      duration: entry.duration,
      // attribution 包含具体函数信息(Chrome)
      culprit: entry.attribution?.[0]?.toJSON()
    })
  }
})

observer.observe({ type: 'longtask', buffered: true })

执行结果示例:

yaml 复制代码
⚠️ 长任务 312ms { name: "self", startTime: 4820.3, duration: 312.1, 
  culprit: { containerName: "window", containerId: "", containerSrc: "" } }

💡 配合 attribution 能定位到是哪个 iframe 或 Worker 触发的长任务。

2️⃣ 监控 LCP(最大内容绘制)

LCP 是 Core Web Vitals 核心指标,用 PerformanceObserver 可以精确知道哪个元素是 LCP 元素:

js 复制代码
const lcpObserver = new PerformanceObserver(list => {
  const entries = list.getEntries()
  const lastEntry = entries[entries.length - 1]
  console.log(`🎨 LCP 元素:${lastEntry.element || lastEntry.url}`, {
    renderTime: lastEntry.renderTime,
    loadTime: lastEntry.loadTime,
    size: lastEntry.size,
    id: lastEntry.id,
    url: lastEntry.url
  })
})

lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true })

实战场景: 发现 LCP 元素是一张 background-image,排查发现图片没有 preload,加上 <link rel="preload"> 后 LCP 从 4.2s 降到 1.8s。

3️⃣ 自动追踪资源加载时序

想知道某个 API 请求为什么慢?不需要改 fetch 代码:

js 复制代码
const resourceObserver = new PerformanceObserver(list => {
  list.getEntries().forEach(entry => {
    if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
      console.log(`🌐 ${entry.name}`, {
        dns: `${entry.domainLookupEnd - entry.domainLookupStart}ms`,
        tcp: `${entry.connectEnd - entry.connectStart}ms`,
        tls: `${entry.secureConnectionStart ? entry.connectEnd - entry.secureConnectionStart : 0}ms`,
        ttfb: `${entry.responseStart - entry.requestStart}ms`,
        download: `${entry.responseEnd - entry.responseStart}ms`,
        total: `${entry.duration}ms`
      })
    }
  })
})

resourceObserver.observe({ type: 'resource', buffered: true })

执行结果示例:

css 复制代码
🌐 https://api.example.com/users
  { dns: "2ms", tcp: "15ms", tls: "28ms", ttfb: "320ms", download: "45ms", total: "410ms" }

一眼看出瓶颈在 TTFB(服务端响应慢),不是网络问题。

4️⃣ 监控 First Paint / First Contentful Paint

js 复制代码
const paintObserver = new PerformanceObserver(list => {
  list.getEntries().forEach(entry => {
    console.log(`🎨 ${entry.name}: ${entry.startTime}ms`)
  })
})

paintObserver.observe({ type: 'paint', buffered: true })
// 🎨 first-paint: 435ms
// 🎨 first-contentful-paint: 435ms

5️⃣ 捕获 Layout Shift(CLS 指标)

js 复制代码
const clsObserver = new PerformanceObserver(list => {
  let cls = 0
  list.getEntries().forEach(entry => {
    if (!entry.hadRecentInput) {
      cls += entry.value
      console.warn(`💥 Layout Shift: ${entry.value.toFixed(3)}`, {
        source: entry.sources?.[0]?.node || 'unknown'
      })
    }
  })
  console.log(`📊 累计 CLS: ${cls.toFixed(3)}`)
})

clsObserver.observe({ type: 'layout-shift', buffered: true })

抓到某个广告位动态插入导致页面跳动,加上 min-height 占位后 CLS 从 0.35 降到 0.05。

要点总结

API 监控目标 关键字段
longtask 主线程卡顿 >50ms duration, attribution
largest-contentful-paint LCP 性能 element, renderTime
resource 资源/请求耗时 initiatorType, duration, 各阶段耗时
paint FP / FCP startTime
layout-shift CLS 布局偏移 value, sources

⚠️ 注意事项

  1. buffered: true 可以拿到注册之前的性能条目------这对首屏分析至关重要
  2. 用完记得 disconnect(),避免性能开销
  3. 并非所有浏览器都支持所有类型,生产环境加 try-catch
  4. 搭配 PerformanceServerTiming 可以拿到服务端下发的自定义指标

一句话总结:PerformanceObserver 是最优雅的「无侵入性能监控方案」------等线上用户反馈卡顿的时候,数据早就抓好了。

相关推荐
知了学历孙老师2 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
ShineWinsu4 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说4 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
程序员老赵6 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源
Swot6 小时前
Asciidoctor 公式数字下标错位问题:原因分析与解决方案
前端
Swot6 小时前
leo-editor 中将 Markdown 内容转成 asciidoc 格式
前端
狒狒AI7 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
修炼的dance7 小时前
浏览器里导出H.265,8.5秒和55.5秒差在硬件
前端
补码缩写7 小时前
WebCodecs选编码器,探测之后还要验解码和播放
前端
大文说跨境7 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化