25 · 前端性能度量与诊断
本篇解决什么问题: "感觉有点卡"不能作为优化的依据。本篇讲清 Core Web Vitals 怎么读、用什么工具测、 怎么用火焰图定位、怎么设性能预算防劣化——先把瓶颈量化,再动刀。
📌 与 A10 的分工:本篇讲度量与诊断(找到瓶颈),A10 讲治理手段(怎么治)。
开篇速览
| 指标 | 衡量什么 | 良好阈值 |
|---|---|---|
| LCP | 最大内容绘制(首屏主要内容出现) | ≤ 2.5s |
| INP | 交互到下一次绘制(响应性) | ≤ 200ms |
| CLS | 累积布局偏移(视觉稳定性) | ≤ 0.1 |
| FCP | 首次内容绘制(有东西出现) | ≤ 1.8s |
| TTFB | 首字节时间(服务端/网络) | ≤ 800ms |
| TBT | 总阻塞时间(主线程被长任务占用) | ≤ 200ms |
一、Core Web Vitals 三兄弟
1.1 LCP(Largest Contentful Paint)
是什么:视口内最大的文字块或图片渲染完成的时间。
常见 LCP 元素:大标题、首屏大图、视频封面。
差的典型原因:
| 原因 | 表现 |
|---|---|
| 首屏大图太大/没优化 | LCP 元素就是那张图 |
| 阻塞渲染的 JS/CSS | 资源下载排队 |
| 服务端慢 | TTFB 就高 |
| 客户端渲染(CSR) | JS 没执行完就没有内容(见 A13) |
首屏图用了 loading="lazy" | ❌ 会推迟 LCP(见 A10) |
1.2 INP(Interaction to Next Paint)
⚠️ INP 已于 2024 年取代 FID 成为核心指标。
是什么:用户交互(点击/输入/键盘)到下一帧绘制的时间,取最差的那一次(或接近最差)。
差的典型原因:
| 原因 | 说明 |
|---|---|
| 长任务(>50ms) | 主线程被占,事件排队等(见 A09) |
| 事件回调里做重活 | 点击后同步处理大量数据 |
| 渲染阻塞 | 交互触发了大范围重渲染 |
| 未防抖的输入 | 每次输入都跑重逻辑 |
INP 分级:≤200ms 好 · 200~500ms 需改进 · >500ms 差。
1.3 CLS(Cumulative Layout Shift)
是什么:页面生命周期内意外布局偏移的累计分数。
典型元凶:
| 元凶 | 解法 |
|---|---|
| 图片/视频没设尺寸 | 必须写 width/height 或 aspect-ratio |
| 广告/嵌入内容动态插入 | 预留固定容器 |
| 字体加载导致文字跳动(FOUT) | font-display: swap + size-adjust |
| 在已有内容上方插入内容 | 改为从下方插入,或用骨架屏占位 |
💡 CLS 只统计非用户交互引发的偏移(点击后 500ms 内的偏移不算)。
二、怎么测:实验室数据 vs 现场数据
| 实验室(Lab) | 现场(Field / RUM) | |
|---|---|---|
| 工具 | Lighthouse、DevTools Performance | web-vitals + 上报 |
| 环境 | 模拟(固定网络/CPU) | 真实用户环境 |
| 用途 | 调试、复现、对比优化前后 | 反映真实体验、发现长尾问题 |
| 局限 | 不代表真实用户 | 难定位具体代码 |
🎯 正确做法:两者都要。用现场数据发现问题,用实验室数据定位与验证。
2.1 实验室:Lighthouse
bash
npx lighthouse https://example.com --view # 命令行
# 或 Chrome DevTools → Lighthouse 面板注意:
- 结果受机器性能、扩展、缓存影响 → 用无痕模式 + 多跑几次取中位数
- 用 CPU/网络限速模拟中低端设备(默认移动配置就是限速的)
2.2 现场:web-vitals 上报
bash
npm i web-vitalsts
import { onLCP, onINP, onCLS } from 'web-vitals'
function send(metric) {
// 用 navigator.sendBeacon 保证页面卸载时也能发出
navigator.sendBeacon('/api/vitals', JSON.stringify({
name: metric.name, // 'LCP' | 'INP' | 'CLS'
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
id: metric.id,
}))
}
onLCP(send); onINP(send); onCLS(send)📌 看 P75(第 75 百分位)而不是平均值——平均值会被少数极快用户拉高,掩盖长尾问题。
三、诊断:用 Performance 面板定位
3.1 录制与读图
DevTools → Performance → 录制(建议勾 Screenshots + Web Vitals)
→ 操作页面 → 停止 → 看这几条泳道| 泳道 | 看什么 |
|---|---|
| Main | 主线程任务,超过 50ms 的叫长任务(右上角标红) |
| Network | 资源下载顺序与瀑布 |
| Frames | 掉帧(红色块) |
| Timings | FCP / LCP / DOMContentLoaded 标记 |
| Interactions | INP 相关:哪个交互慢 |
3.2 定位长任务(INP 的关键)
- 在 Main 泳道找到红色角标的长任务
- 点开看 Bottom-Up / Call Tree,找到耗时最长的函数
- 常见结果:
Layout/Recalculate Style很大 → 布局抖动或样式规则过多(见 A10)- 某个业务函数很长 → 拆分或挪到 Worker
XHR后紧跟大段 JS → 数据处理太重
3.3 定位 LCP
- 在 Timings 里找到 LCP 标记
- 看它之前阻塞了什么:是哪个资源(大图/字体/JS)在拖
- 若是图片 → 检查格式、尺寸、
preload、fetchpriority
3.4 定位 CLS
- DevTools → Performance → 勾选
Experience→ 会标出 Layout Shift 记录 - 或 Rendering 面板 → 勾
Layout Shift Regions,页面里直接高亮抖动区域
四、性能预算:防止劣化
问题:优化完过几个月又变慢了——因为没人守着。
解法:把性能变成 CI 的一道工序。
js
// lighthouse-budgets.json(示例)
{
"resourceSizes": [
{ "resourceType": "script", "budget": 300 }, // KB
{ "resourceType": "image", "budget": 400 },
{ "resourceType": "total", "budget": 1000 }
],
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 }, // ms
{ "metric": "cumulative-layout-shift", "budget": 0.1 }
]
}bash
npx lighthouse https://example.com --budget-path=./budgets.json --view
# 超预算 → 退出码非 0 → CI 失败体积预算更简单的方式(Vite):
js
build: {
rollupOptions: { output: { manualChunks: {...} } },
chunkSizeWarningLimit: 500, // 单 chunk 超过 500KB 就告警
}🎯 预算要写进 CI,否则等于没有。团队约定"超预算不许合入"。
五、诊断方法论(排查流程)
① 现象:用户说"卡" → 先问清**什么操作、什么设备、什么网络**
② 量化:现场数据(web-vitals P75)确认是真的差,还是个别 case
③ 复现:DevTools 限速(CPU 4× / 4G)下复现
④ 录制:Performance 面板录制,按 LCP / INP / CLS 分类定位
⑤ 归因:
- TTFB 高 → 服务端/CDN 问题(不是前端)
- LCP 高 → 资源体积/阻塞/渲染模式
- INP 高 → 长任务(主线程被占)
- CLS 高 → 尺寸未预留/动态插入
⑥ 治理:按 [A10](./10-Web性能优化.md) 选手段
⑦ 验证:复测 + 加预算防劣化自检清单
- [ ] 能说出 LCP / INP / CLS 各自衡量什么、良好阈值是多少
- [ ] 知道 INP 已取代 FID
- [ ] 能区分实验室数据与现场数据的用途,并知道看 P75
- [ ] 能用 Performance 面板定位长任务、LCP 元素、布局偏移
- [ ] 能把性能预算写进 CI 防止劣化
常见误区
| ❌ 误区 | ✅ 事实 |
|---|---|
| "Lighthouse 跑 90 分就没问题" | 那是模拟环境;要看真实用户 P75 |
| "看平均加载时间" | 平均值掩盖长尾 → 看 P75/P95 |
| "优化完就结束了" | 不加预算会慢慢劣化回去了 |
| "CLS 高是小事" | 直接影响体验与 SEO,而且通常很好修(加尺寸) |
| "INP 高就是机器慢" | 多数是自己的长任务占着主线程 |
动手练习
- 对本站点跑 Lighthouse,记录 LCP/INP/CLS,找出 CLS 的具体元素并修复(多半是图片没尺寸)。
- 在 CPU 4× 降速下录一段交互,找出最长的那个任务,说出它属于哪一类(布局/脚本/样式)并给出治理手段。
- 接入
web-vitals并上报到你们的监控,观察真实用户的 P75 与本地 Lighthouse 分数差多少。
相关文档
- 找到瓶颈后的治理手段 → A10 · Web 性能优化
- 主线程与长任务原理 → A09 · JavaScript 运行时
- 渲染模式对 LCP 的影响 → A13 · 渲染模式
- 线上监控与告警 → A12 · 前端可观测性与线上排障