文章

前端性能监控深度解析:Performance API与Web Vitals实战

没监控就没优化:性能监控用 Performance API 与 PerformanceObserver 采集 LCP、INP、CLS 等 Web Vitals,通过 RUM 替代实验室数据。 让你知道真实用户在低端机上等了几秒,面试常考 PerformanceTimeline、PerformanceObserver 与实验室、现场数据的区别。

前端性能监控深度解析:Performance API与Web Vitals实战

一句话概括

没监控就没优化——性能监控用 Performance API 和 PerformanceObserver 精准采集 LCP/INP/CLS 等 Web Vitals 指标,通过 RUM(真实用户监控)替代实验室数据,让你知道用户在 4G 低端机上到底等了多少秒。

核心知识点

1. Performance API:浏览器内置的性能时间线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 最常用的两个入口
// 1. performance.now():高精度时间戳(微秒级),用于手动打点
const start = performance.now();
doHeavyWork();
console.log(`耗时: ${performance.now() - start}ms`);

// 2. performance.getEntriesByType():获取各类性能记录
const nav = performance.getEntriesByType('navigation')[0];
// { domContentLoaded: 320, loadComplete: 560, ... }

const resources = performance.getEntriesByType('resource');
// [ { name: 'https://cdn.xxx/app.js', duration: 120, transferSize: 45000 }, ... ]

// 3. PerformanceObserver:异步监听性能事件(推荐,不阻塞主线程)
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.startTime}ms`);
  }
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });

2. 三大核心 Web Vitals 采集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// LCP(最大内容绘制)—— 用户看到核心内容的速度
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lcp = entries[entries.length - 1]; // 取最后一个(最终值)
  console.log(`LCP: ${lcp.renderTime || lcp.loadTime}ms`);
}).observe({ type: 'largest-contentful-paint', buffered: true });

// INP(交互到下次绘制)—— 代替 FID 的新标准(2024 年)
let maxINP = 0;
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const duration = entry.processingEnd - entry.startTime; // 完整交互延迟
    if (duration > maxINP) maxINP = duration;
  }
}).observe({ type: 'event', durationThreshold: 16, buffered: true });
// 页面卸载时上报 maxINP

// CLS(累计布局偏移)—— 页面视觉稳定性
let cls = 0;
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) cls += entry.value; // 排除用户交互导致的偏移
  }
}).observe({ type: 'layout-shift', buffered: true });

3. 自定义性能打点:User Timing API

1
2
3
4
5
6
7
8
9
10
11
12
// 业务关键路径打点
performance.mark('search-start');
await fetchSearchResults();
performance.mark('search-end');
performance.measure('search-duration', 'search-start', 'search-end');

const measure = performance.getEntriesByName('search-duration')[0];
console.log(`搜索耗时: ${measure.duration}ms`);

// 首屏组件渲染完成时间
// React: 在 useEffect 中打点
useEffect(() => { performance.mark('homepage-rendered'); }, []);

4. RUM 数据上报策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 关键:在页面卸载前把数据发出去
function reportWebVitals() {
  const vitals = { lcp: 0, inp: 0, cls: 0, ttfb: 0 };
  // ... 采集逻辑

  // 用 sendBeacon 保证卸载时也能发送(不阻塞页面关闭)
  const blob = new Blob([JSON.stringify(vitals)], { type: 'application/json' });
  navigator.sendBeacon('/api/vitals', blob);

  // 或用 fetch + keepalive
  // fetch('/api/vitals', { method: 'POST', body: JSON.stringify(vitals), keepalive: true });
}
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') reportWebVitals();
});

5. Lighthouse vs RUM:两种互补的监控手段

维度Lighthouse(实验室)RUM(真实用户)
数据来源模拟设备 + 固定网络真实用户设备 + 真实网络
优点可复现、A/B 对比反映真实体验分布
缺点可能和实际差距大噪音多、需要 P75/P95 分析
用途开发阶段诊断线上持续监控

实用技巧:Lighthouse 拿 100 分不代表用户不卡。一定要看 RUM 的 P75——它是”大多数用户”的真实体验。

其实你每天都在用

  • Chrome DevTools Performance 面板:你在 DevTools 里点”录制”然后操作页面,就是在用 Performance API 的数据做分析
  • Lighthouse 报告:每次跑 Lighthouse 背后的 LCP/CLS/TBT 数据都来自 PerformanceObserver
  • 页面卡顿感知:你感觉”这个网页好卡”→ 本质是 INP 超过 200ms,每次交互到画面更新间隔太长
  • CLS 受害者:打开网页刚要点”购买”,按钮突然被广告挤走了——这就是 CLS 在坑你,CLS > 0.25 就算差

常见误解(FAQ)

❌ 误区:FCP 早等于页面快

FCP(首次内容绘制)只表示”内容出现了”,可能是空白骨架屏或 loading spinner。真正的体验看 LCP(最大内容绘制完成)。FCP 1.2s 但 LCP 5s 的页面,用户绝对不会觉得快。

❌ 误区:用 setTimeout 采集就够精确

setTimeout 最小 4ms(嵌套超过 5 层后),且受事件循环影响不精确。用 performance.now()(精度微秒级)和 PerformanceObserver 才是正确方式。

❌ 误区:CLS 低就一定没有布局偏移

CLS 只统计”意外”偏移。如果用户主动交互(点击展开)导致的偏移不计入 CLS。而且 CLS 是累积值——一个页面可以多次偏移但每次都很小,总 CLS 低但体验很差(”抖动感”)。INP + CLS 一起看才完整。

❌ 误区:Performance API 数据可以直接发到后端分析

浏览器有隐私保护机制——transferSize、serverTiming 等字段在跨域资源上可能返回 0。TAO(Timing-Allow-Origin)响应头需要服务端配合设置。

一句话总结

性能优化的第一步永远是建立正确的度量——不看 Lighthouse 分数看 P75,不只看平均值看分布,因为 100 个用户里 99 个快、1 个在 2G 网络上等 10 秒,平均值看起来还不错,但那 1 个用户已经流失了。

本文由作者按照 CC BY 4.0 进行授权