前端性能优化
# 05.前端性能优化
Core Web Vitals、资源加载优化、渲染性能、Bundle 分析——掌握"网页快不快"的测量与优化方法。
# 1. 案例引入:用户的 3 秒忍耐极限
某电商首页的真实 Lighthouse 报告:
FCP (First Contentful Paint) → 3.1 秒 🔴 差
LCP (Largest Contentful Paint) → 5.8 秒 🔴 差
TBT (Total Blocking Time) → 890 ms 🔴 差
CLS (Cumulative Layout Shift) → 0.32 🔴 差
结果:跳出率 58%,转化率比同行业低 40%
优化后:
FCP → 0.8 秒 🟢 好 (减少图片阻塞 + 延迟非关键 JS)
LCP → 1.6 秒 🟢 好 (大图懒加载 + CDN)
TBT → 120 ms 🟢 好 (JS 代码分割 + Worker)
CLS → 0.01 🟢 好 (图片/广告预设尺寸)
# 2. Core Web Vitals——Google 的性能三指标
# 2.1 LCP(最大内容绘制)← "主要内容可见速度"
LCP 测量的是:视口中最大元素(图片/文本块)加载完成的时间
目标:< 2.5 秒 → 好
2.5~4.0 秒 → 需要改进
> 4.0 秒 → 差
优化方向:
1. 减少服务器响应时间(CDN/TTFB)
2. 优先加载首屏关键资源(preload 关键 CSS/图片)
3. 延迟加载非首屏元素(lazy loading)
# 2.2 FID / INP(交互延迟)← "页面响应速度"
FID:用户首次交互(点击/输入)到浏览器响应的时间(即将被 INP 取代)
INP:页面生命周期内最长/最卡的那次交互延迟
目标:< 100 ms → 好
< 200 ms → 需要改进
> 500 ms → 差
优化方向:
1. 拆分长任务(long task > 50ms)
2. 代码分割(首屏只加载必要的 JS)
3. Web Worker 处理密集计算(不阻塞主线程)
# 2.3 CLS(累计布局偏移)← "页面跳动幅度"
CLS 测量的是:页面加载过程中,元素突然"跳"到新位置的累积偏移
常见原因:
1. 图片没有预设尺寸 → 加载后撑开布局
2. 动态插入广告 → 已有内容被推开
3. Web 字体加载 → 文本大小改变
目标:< 0.1 → 好
0.1~0.25 → 需要改进
> 0.25 → 差
优化方向:
1. 所有 <img> / <video> 必须设置 width/height
2. 动态内容预留空间(如广告占位 div)
3. 使用 font-display: swap(减少字体闪烁)
# 3. 资源加载优化
# 3.1 关键渲染路径优化
浏览器渲染一个页面的完整链路:
HTML 解析
↓ 遇到 <link rel="stylesheet">
CSS 下载 + 解析(阻塞渲染!)
↓ 遇到 <script>
JS 下载 + 执行(阻塞解析!HTML 解析暂停)
↓ HTML 继续解析
DOM Tree + CSSOM Tree → Render Tree → Layout → Paint
优化策略:
1. CSS:关键 CSS 内联在 <head> 中(消除外部请求)
2. JS:非关键脚本 defer(延迟执行)或 async(异步下载)
3. 资源提示:<link rel="preload"> 提前加载关键资源
# 3.2 资源提示(Resource Hints)
<head>
<!-- preload:告诉浏览器"这个资源 ASAP 加载"(用于当前页面关键资源) -->
<link rel="preload" href="/fonts/Main.woff2" as="font" crossorigin>
<link rel="preload" href="/hero-image.webp" as="image">
<!-- prefetch:告诉浏览器"这个资源可能下个页面用"(低优先级) -->
<link rel="prefetch" href="/page2.js">
<!-- preconnect:提前建立连接(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://api.example.com">
<!-- dns-prefetch:提前 DNS 解析 -->
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- 脚本加载策略 -->
<script src="/app.js" defer></script> <!-- HTML 解析完后执行,保持顺序 -->
<script src="/analytics.js" async></script> <!-- 下载完立刻执行,不保证顺序 -->
</head>
# 3.3 图片优化
图片是前端最大的字节消耗(平均占页面 45%~60%)
策略:
1. 选格式:照片用 WebP/AVIF(比 JPEG 小 25~35%),图标用 SVG
2. 设尺寸:<img width="800" height="600"> 防止 CLS
3. 响应式:<img srcset="small.webp 400w, large.webp 800w" sizes="(max-width:600px) 400px, 800px">
4. 懒加载:<img loading="lazy"> 原生支持
5. CDN + 压缩:通过 CDN 的图片处理服务在线转换格式和尺寸
# 3.4 字体优化
@font-face {
font-family: 'CustomFont';
src: url('/fonts/CustomFont.woff2') format('woff2');
font-display: swap; /* 马上用系统字体,加载完后替换 */
/* 替代:
font-display: optional 极短等待(~100ms)后不替换
auto 浏览器默认
block 一直白屏等字体(不推荐) */
}
# 4. 渲染性能
# 4.1 理解浏览器渲染流水线
HTML
↓ 解析
DOM Tree
↓ + CSSOM Tree ← 这个阶段被 CSS 阻塞
Render Tree (结合 DOM + 样式,隐藏节点不在树上)
↓
Layout (计算每个元素的位置和尺寸)
↓
Paint (绘制像素到图层)
↓
Composite (把各图层合成到屏幕)
性能瓶颈在这三个阶段:
Layout → 重排(reflow):修改几何属性触发
Paint → 重绘(repaint):仅修改外观触发
Composite → 合成:最便宜,不触发 Layout/Paint
# 4.2 减少重排和重绘
/* 触发重排的操作(尽量避免在动画中使用) */
element.offsetWidth; /* 读取几何属性——强制同步 Layout */
element.style.width; /* 修改几何属性 */
element.classList.add; /* 修改可能改变布局的 class */
/* 只触发重绘(比重排好,但仍消耗) */
element.style.color
element.style.background
/* 只触发合成(性能最佳) */
element.style.transform /* translate / scale / rotate */
element.style.opacity
/* 批量 DOM 修改 */
// ❌ 每次循环都触发 Layout
for (let i = 0; i < 100; i++) {
element.style.width = i + 'px';
}
// ✅ 用 requestAnimationFrame 合并
requestAnimationFrame(() => {
for (let i = 0; i < 100; i++) {
element.style.width = i + 'px';
}
});
# 4.3 CSS containment
.widget {
contain: layout style paint;
/* 告诉浏览器:这个元素的内部变化不会影响外部 */
/* 浏览器可以跳过全局 Layout,只在内部重新计算——大幅提升性能 */
}
/* 值的组合:
content-visibility: auto = 视口外的元素自动跳过渲染(推荐) */
.lazy-section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* 占位高度,防滚动条跳动 */
}
# 5. Bundle 分析与优化
# 5.1 分析打包体积
# Vite 的打包分析
npm run build
# 自动生成 dist/stats.html → 浏览器打开查看各模块大小
# Webpack 的打包分析
npx webpack --profile --json > stats.json
# 上传到 https://webpack.github.io/analyse/
# 或用 rollup-plugin-visualizer(兼容 Webpack/Vite)
# 5.2 常见体积杀手
常见大体积来源和解决方案:
1. moment.js (232KB) → dayjs (2KB):API 几乎兼容
2. lodash 全量 (72KB) → lodash-es (按需引入) 或直接用原生 API
3. Element Plus 全量 → unplugin-vue-components 按需引入
4. 未压缩的图片 → 切换到 WebP/AVIF + CDN 转换
5. node_modules 重复打包 → splitChunks 提取 vendor
# 5.3 按需引入
// ❌ 全量引入
import moment from 'moment';
// ✅ 按需引入 + Tree Shaking
import dayjs from 'dayjs'; // 2KB
// ❌
import { debounce } from 'lodash';
// ✅
import debounce from 'lodash-es/debounce'; // 只引入一个函数 ~1KB
// 或者用原生实现
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
# 6. 缓存策略
文件类型 缓存策略 原因
─────────────────────────────────────────────────────────
index.html no-cache (每次验证) 入口文件必须是最新
app.[hash].js max-age=31536000, immutable 有 hash,永久缓存
vendor.[hash].js max-age=31536000, immutable 第三方库很少变
[hash].css max-age=31536000, immutable 同 JS
logo.[hash].png max-age=31536000, immutable 有 hash
fonts/ max-age=31536000 字体变化少
Nginx 配置:
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
add_header Cache-Control "no-cache"; # 每次验证
}
# 7. 性能监控
# 7.1 测量 Core Web Vitals
// 使用 web-vitals 库
import { onCLS, onINP, onLCP } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
// 发送到自建埋点或 Google Analytics
navigator.sendBeacon('/api/vitals', JSON.stringify({ name, value, id }));
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
# 7.2 性能预算
设定性能目标,CI 中自动检测:
Total JS bundle < 200KB (gzip)
Total CSS bundle < 50KB (gzip)
LCP < 2.5s
TBT < 200ms
使用 bundlesize 或 lighthouse-ci 在 PR 中自动报告
# 8. 速查表
| 问题 | 检查方法 | 优化方向 |
|---|---|---|
| 首屏慢 | Lighthouse LCP | preload 关键资源、延迟非关键 JS |
| 交互卡 | Lighthouse TBT / INP | 拆分长任务、代码分割 |
| 布局跳动 | 手动测试 / CLS | 预设图片尺寸、预留动态内容空间 |
| 包体积大 | rollup-plugin-visualizer | 按需引入、Tree Shaking、chunk 拆分 |
| 图片太大 | Lighthouse 图片审计 | WebP/AVIF、srcset、CDN |
| 字体阻塞 | 网络面板 | font-display:swap、preload 字体 |
| 缓存失效 | 响应头 | contenthash + immutable |
| 重排频繁 | Performance 面板 | 批量 DOM、requestAnimationFrame |
性能优化三问(每个优化决策前):
- 问题真的是瓶颈吗?(先用 Lighthouse 测量)
- 优化后对用户体验改善多少?(设定可量化的目标)
- 优化的代价是什么?(开发成本 vs 性能收益)
专栏完结。回顾 5 篇:01.前端工程化概述 → 02.Webpack构建实战 → 03.Vite构建实战 → 04.代码规范与质量 → 05.前端性能优化
上次更新: 2026/06/24, 14:17:21