性能监控|浏览器 API | 你以为 Performance API 只是
performance.now()?PerformanceObserver能在不阻塞主线程的前提下监听到 LCP、FMP、长任务、内存泄漏......甚至能捕获「谁在偷偷做耗时的同步操作」。
问题场景
线上项目偶尔出现卡顿,但在 DevTools 里抓不到复现。打点日志也看不出端倪------因为你只能看到结果 (页面慢了),看不到原因(谁占用了主线程)。
js
// 传统的打点方式
const start = performance.now()
doHeavyWork()
console.log(`耗时:${performance.now() - start}ms`)
这种方式有两个致命缺陷:
- 侵入性强------要改造业务代码
- 抓不到偶发问题------你不知道什么时候该打点
解决方案: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 |
⚠️ 注意事项
buffered: true可以拿到注册之前的性能条目------这对首屏分析至关重要- 用完记得
disconnect(),避免性能开销 - 并非所有浏览器都支持所有类型,生产环境加
try-catch - 搭配
PerformanceServerTiming可以拿到服务端下发的自定义指标
一句话总结:
PerformanceObserver是最优雅的「无侵入性能监控方案」------等线上用户反馈卡顿的时候,数据早就抓好了。