Files
test-repo/docs/zh-cn/appendix/frontend-performance.md
T

852 lines
20 KiB
Markdown
Raw Normal View History

# 前端性能优化:从加载到渲染 (Interactive Guide to Frontend Performance)
> 💡 **学习指南**:本章节通过交互式演示和实战案例,帮你建立前端性能优化的完整知识体系。性能优化不是玄学,而是一套可测量、可优化的工程方法论。
## 0. 引言:为什么性能很重要?
**性能就是用户体验。**
一个页面加载多慢,用户就会流失?研究数据表明:
- **3 秒法则**:页面加载超过 3 秒,53% 的用户会离开
- **0.1 秒延迟**:亚马逊发现页面延迟 0.1 秒,销售额下降 1%
- **移动端更敏感**:移动用户对性能的容忍度更低
性能优化不只是"让页面变快",而是:
- **提升转化率**:更快的加载 = 更多的订单/注册
- **改善 SEO**:搜索引擎优先索引加载快的页面
- **降低成本**:优化的资源 = 更少的带宽和服务器成本
### 0.1 核心性能指标 (Core Web Vitals)
Google 定义了三个核心性能指标,所有网页都应该达标:
<PerformanceMetricsDemo />
**关键点**:性能优化要关注真实用户感受到的体验,而不只是技术指标。
### 0.2 性能预算 (Performance Budget)
**性能预算**是为项目设定的性能限制,比如:
- JavaScript 文件不超过 200KB
- 首屏加载时间不超过 2 秒
- 总资源大小不超过 1MB
**为什么需要预算?**
- 防止项目膨胀:随着功能增加,性能很容易恶化
- 团队协作规范:新功能上线前必须通过性能检查
- 持续优化动力:每次迭代都有改进目标
---
## 1. 性能分析工具
在优化之前,先学会**测量**。只有找到瓶颈,才能精准优化。
### 1.1 Chrome DevTools
浏览器自带的开发者工具,功能强大:
**Performance 面板**
- 录制页面运行过程
- 查看 FPS(帧率)、CPU 使用率
- 分析 JavaScript 执行时间
- 找出长任务(Long Tasks,超过 50ms 的 JS 任务)
**Lighthouse 面板**
- 一键生成性能报告
- 包含性能、可访问性、最佳实践、SEO 等评分
- 给出具体优化建议
**Network 面板**
- 查看所有资源加载时间
- 分析瀑布图(Waterfall
- 找出加载慢的资源
### 1.2 WebPageTest
在线性能测试工具([webpagetest.org](https://webpagetest.org)):
- 多地点测试(全球各地)
- 真实浏览器测试(Chrome、Firefox、Safari
- 丰富的测试报告和视频
- 可以模拟不同网速(3G、4G
### 1.3 PageSpeed Insights
Google 官方工具([pagespeed.web.dev](https://pagespeed.web.dev)):
- 基于 Core Web Vitals 评分
- 区分移动端和桌面端
- 提供字段数据(真实用户数据)和实验室数据
**关键点**:用数据驱动优化,而不是凭感觉。
---
## 2. 加载优化 (Loading Optimization)
加载优化是性能优化的第一步:让资源更快地到达浏览器。
### 2.1 资源压缩
**文本压缩**:使用 Gzip 或 Brotli 压缩 HTML、CSS、JavaScript
```
未压缩: 500 KB
Gzip: 150 KB (压缩率 70%)
Brotli: 120 KB (压缩率 76%)
```
**开启方法**Nginx 配置):
```nginx
gzip on;
gzip_types text/css application/javascript;
gzip_min_length 1000;
# Brotli 需要额外模块
brotli on;
brotli_types text/plain text/css application/javascript;
```
**图片压缩**:使用工具(如 ImageOptim、TinyPNG)压缩图片
<LazyLoadingDemo />
### 2.2 代码分割 (Code Splitting)
**问题**:传统打包方式把所有代码打包成一个文件,首屏要下载大量无用代码
**解决方案**:按路由或功能分割代码,用户只下载当前页面需要的代码
**示例**Vite + Vue Router):
```javascript
// 懒加载路由组件
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
```
**效果**
- 首页只加载 100KB(而不是 500KB
- 用户访问 /about 时才加载额外代码
- 整体首屏时间减少 60%
### 2.3 Tree Shaking
**Tree Shaking**:移除未使用的代码
**示例**
```javascript
// 整个 lodash 库:70 KB
import _ from 'lodash'
// 只用某个函数:2 KB
import debounce from 'lodash/debounce'
```
**Tree Shaking 原理**
- ES Module 的 `import/export` 是静态结构
- 打包工具(Webpack、Vite)可以分析哪些代码被使用
- 未使用的代码在打包时被删除
### 2.4 预加载 (Preloading)
**预加载关键资源**:告诉浏览器提前加载重要资源
```html
<!-- 预加载关键 CSS -->
<link rel="preload" href="critical.css" as="style">
<!-- 预加载字体 -->
<link rel="preload" href="font.woff2" as="font" crossorigin>
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com">
```
**优先级**
- `preload`:立即下载(但可能抢占关键资源)
- `prefetch`:空闲时下载(适合下一页资源)
- `preconnect`:提前建立 TCP 连接
### 2.5 CDN 加速
**CDNContent Delivery Network**:内容分发网络
**工作原理**
- 把静态资源部署到全球各地的服务器
- 用户从最近的服务器下载资源
- 减少网络传输延迟
**使用建议**
- 图片、字体、CSS、JS 等静态资源放 CDN
- 使用公共 CDN(如 unpkg、jsDelivr)加载第三方库
- 大型网站使用自建 CDN 或商业 CDN(如 Cloudflare
**效果**
- 国内用户加载速度提升 50%-80%
- 海外用户体验显著改善
---
## 3. 渲染优化 (Rendering Optimization)
资源加载完成后,浏览器要"画"出页面。渲染优化让这个过程更快。
### 3.1 关键渲染路径 (Critical Rendering Path)
**浏览器渲染流程**
1. **解析 HTML** → 构建 DOM 树
2. **解析 CSS** → 构建 CSSOM 树
3. **合并 DOM + CSSOM** → 构建渲染树
4. **布局(Layout**:计算元素位置和大小
5. **绘制(Paint**:绘制像素
6. **合成(Composite**:合成图层
**关键点**:每一步都可能成为性能瓶颈。
### 3.2 DOM 优化
**减少 DOM 操作**DOM 操作很慢,批量处理
**示例**(低效):
```javascript
// 每次循环都触发重排
for (let i = 0; i < 1000; i++) {
document.body.innerHTML += `<div>${i}</div>`
}
```
**优化**
```javascript
// 使用文档片段,只触发一次重排
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div')
div.textContent = i
fragment.appendChild(div)
}
document.body.appendChild(fragment)
```
**使用虚拟 DOM**
- Vue、React 使用虚拟 DOM 减少真实 DOM 操作
- 批量更新,减少重排次数
### 3.3 CSS 优化
**减少 CSS 大小**
- 移除未使用的 CSS(使用 PurgeCSS
- 压缩 CSS(移除空格、注释)
**优化 CSS 选择器**
```css
/* 慢:从右向左匹配 */
.container div ul li a { }
/* 快:使用类选择器 */
.link { }
```
**关键 CSS 内联**
- 把首屏需要的 CSS 内联到 HTML
- 减少渲染阻塞
```html
<style>
/* 首屏关键 CSS */
.header { }
.hero { }
</style>
```
### 3.4 重排与重绘
<ReflowRepaintDemo />
**触发重排的操作**
- 改变元素大小、位置
- 添加/删除 DOM 元素
- 改变字体大小
- 改变窗口大小
**触发重绘的操作**
- 改变颜色
- 改变背景
- 改变边框样式
**优化建议**
- 批量修改样式(使用 class
- 使用 `transform``opacity`(触发合成)
- 避免逐帧修改样式(使用 requestAnimationFrame
### 3.5 合成层 (Compositing)
**使用 `will-change` 提示浏览器**
```css
.animated-element {
will-change: transform, opacity;
}
```
**使用 GPU 加速**
```css
.gpu-accelerated {
transform: translateZ(0);
/* 或 */
transform: translate3d(0, 0, 0);
}
```
**注意**:不要滥用合成层,过多会消耗内存。
### 3.6 虚拟列表 (Virtual List)
当需要展示成千上万条数据时(如长列表、聊天记录),如果直接渲染所有 DOM 节点,会导致:
- **DOM 节点过多**:占用大量内存
- **渲染缓慢**:样式计算和布局耗时增加
- **滚动卡顿**:浏览器无法维持 60fps
**解决方案**:只渲染**可视区域**内的元素,加上少量缓冲区。
<VirtualScrollingDemo />
**核心原理**
1. 计算可视区域能容纳多少个元素。
2. 监听滚动事件,根据 `scrollTop` 计算当前应该渲染数据的 `startIndex``endIndex`
3. 使用 `padding-top``transform` 将渲染的内容定位到正确位置。
---
## 4. JavaScript 优化
JavaScript 是页面的"肌肉",优化它让页面更流畅。
### 4.1 代码压缩 (Minification)
**移除不必要的字符**
- 空格、换行、注释
- 缩短变量名
- 移除无用代码
**工具**
- **Terser**JavaScript 压缩工具
- **ESBuild**:极快的打包工具
- **Vite**:开发环境使用 ESBuild,生产环境使用 Rollup
**示例**
```javascript
// 原始代码
function calculateTotal(price, quantity) {
return price * quantity
}
// 压缩后
function calculateTotal(a,b){return a*b}
```
### 4.2 防抖与节流
**防抖 (Debounce)**:事件触发后,等待一段时间再执行
```javascript
// 搜索框输入:停止输入 300ms 后才搜索
const debouncedSearch = debounce((keyword) => {
searchAPI(keyword)
}, 300)
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value)
})
```
**节流 (Throttle)**:限制函数执行频率
```javascript
// 滚动事件:最多每 100ms 执行一次
const throttledScroll = throttle(() => {
updatePosition()
}, 100)
window.addEventListener('scroll', throttledScroll)
```
### 4.3 Web Workers
**Web Workers**:在后台线程运行 JavaScript,不阻塞主线程
**使用场景**
- 大数据计算
- 图片/视频处理
- 复杂算法
**示例**
```javascript
// 主线程
const worker = new Worker('calculator.js')
worker.postMessage({ data: largeData })
worker.onmessage = (e) => {
console.log('计算结果:', e.data.result)
}
// calculator.js (Worker 线程)
self.onmessage = (e) => {
const result = heavyCalculation(e.data.data)
self.postMessage({ result })
}
```
### 4.4 避免长任务
**长任务(Long Task**:执行时间超过 50ms 的任务
**问题**:长任务会阻塞主线程,导致页面卡顿
**解决方案**
- **时间切片**:把大任务拆成小任务
- **使用 requestIdleCallback**:在浏览器空闲时执行
- **Web Workers**:移到后台线程
**示例**(时间切片):
```javascript
async function processLargeArray(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i])
// 每 100 个项目暂停一次,让浏览器处理其他任务
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0))
}
}
}
```
---
## 5. 图片优化
图片通常是网页最大的资源,优化图片能显著提升性能。
### 5.1 格式选择
<ImageOptimizationDemo />
**格式对比**
| 格式 | 大小 | 质量 | 浏览器支持 | 适用场景 |
|------|------|------|-----------|----------|
| JPEG | 小 | 好 | 所有 | 照片 |
| PNG | 大 | 最好 | 所有 | 透明图片、图标 |
| WebP | 很小 | 好 | 现代浏览器 | 大部分场景 |
| AVIF | 最小 | 很好 | 最新浏览器 | 追求极致性能 |
**建议**
- 优先使用 WebP
- 提供降级方案(JPEG/PNG
```html
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述">
</picture>
```
### 5.2 响应式图片
**根据屏幕尺寸加载不同尺寸的图片**
```html
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w
"
sizes="(max-width: 600px) 400px, 800px"
alt="响应式图片"
>
```
**解释**
- `srcset`:定义不同尺寸的图片
- `sizes`:告诉浏览器图片在不同屏幕上的显示尺寸
- 浏览器自动选择最合适的图片
### 5.3 懒加载
<LazyLoadingDemo />
**图片懒加载**:只有当图片进入视口时才加载
**方法 1:使用 `loading` 属性**
```html
<img src="image.jpg" loading="lazy" alt="懒加载图片">
```
**方法 2:使用 Intersection Observer**
```javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img)
})
```
### 5.4 压缩与裁剪
**使用工具压缩图片**
- **ImageOptim**Mac
- **TinyPNG**(在线)
- **Squoosh**Google 开源)
**使用 CDN 实时裁剪**
```html
<!-- 使用 CDN 裁剪为 800x600 -->
<img src="https://cdn.example.com/image.jpg?w=800&h=600&q=80">
```
---
## 6. 字体优化
字体也会影响性能,不当的字体加载会导致 FOUT/FOIT。
### 6.1 Web Font 优化
**问题**:使用 Web Font 时,浏览器可能:
- **FOUT**Flash of Unstyled Text):先显示系统字体,然后切换到 Web Font
- **FOIT**Flash of Invisible Text):文字隐藏,等 Web Font 加载完才显示
### 6.2 Font Display 策略
```css
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap; /* 立即显示系统字体,Web Font 加载完再切换 */
}
```
**`font-display` 值**
- `auto`:浏览器默认
- `swap`:立即显示文本,Web Font 加载后替换(推荐)
- `fallback`:短时间隐藏,超时后显示系统字体
- `optional`:如果 Web Font 加载慢,就不使用它
### 6.3 字体子集化
**只包含用到的字符**
- 中文字体很大(几 MB),但通常只用几百个字
- 使用工具提取子集
**工具**
- **Fontmin**(中文字体子集化)
- **glyphhanger**(提取页面实际使用的字符)
**示例**
```bash
# 只提取常用的 500 个汉字
fontmin input.ttf output/ --text='常用的五百个汉字...'
```
**结果**
- 原始字体:5 MB
- 子集化后:200 KB
- 减少 96%
---
## 7. 缓存策略
缓存是性能优化的"银弹",用好了能极大提升性能。
### 7.1 HTTP 缓存
**强缓存(Strong Cache**
```nginx
# 静态资源缓存 1 年
location ~* \.(jpg|png|css|js)$ {
expires: 1y;
add_header Cache-Control: public, immutable;
}
```
**协商缓存(Conditional Cache**
```nginx
# 使用 ETag
location / {
etag on;
}
```
**最佳实践**
- 带哈希的文件名(如 `app.abc123.js`):永久缓存
- 不带哈希的文件:协商缓存
### 7.2 Service Worker
**Service Worker**:在浏览器后台运行的脚本,可以拦截网络请求
**核心能力**
- 离线访问
- 资源缓存
- 后台同步
**示例**(使用 Workbox):
```javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
// sw.js (Service Worker 脚本)
workbox.routing.registerRoute(
/\.(?:png|jpg|jpeg|svg|gif)$/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 天
}),
],
})
)
```
### 7.3 LocalStorage / IndexedDB
**LocalStorage**:存储简单数据(5-10 MB
```javascript
// 缓存 API 数据
localStorage.setItem('cache_key', JSON.stringify(data))
const cached = JSON.parse(localStorage.getItem('cache_key'))
```
**IndexedDB**:存储大量结构化数据
```javascript
// 存储离线数据
const db = await openDB('mydb', 1, {
upgrade(db) {
db.createObjectStore('posts')
}
})
await db.put('posts', postData, 'post-1')
```
---
## 8. 监控与持续优化
性能优化不是一次性的工作,需要持续监控和改进。
### 8.1 Real User Monitoring (RUM)
**RUM**:收集真实用户的性能数据
**工具**
- **Google Analytics**:免费,基础数据
- **Cloudflare Web Analytics**:免费,注重隐私
- **SpeedCurve**:付费,专业级
**关键指标**
- 首屏时间(FCP、LCP
- 交互时间(TTI
- 转化率与性能的关系
### 8.2 Synthetic Monitoring
**合成监控**:用模拟用户定期测试
**工具**
- **Lighthouse CI**:每次提交代码自动测试
- **WebPageTest**:定期测试关键页面
- **Pingdom**:简单易用的监控服务
### 8.3 性能预算
**设置预算并强制执行**
```javascript
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router'],
'ui': ['element-plus']
}
}
}
}
})
```
**使用 Lighthouse CI 检查预算**
```json
// lighthouserc.json
{
"ci": {
"assert": {
"preset": "desktop",
"assertions": {
"first-contentful-paint": ["warn", { "maxNumericValue": 2000 }],
"interactive": ["error", { "maxNumericValue": 5000 }]
}
}
}
}
```
---
## 9. 实战案例
### 9.1 案例 1:优化一个慢页面
**问题**:一个电商商品页加载需要 8 秒
**诊断**
- 图片总和:3 MB
- JavaScript1.2 MB
- CSS400 KB
- 45 个资源请求
**优化措施**
1. 压缩图片 → 减少 60%(1.2 MB)
2. 使用 WebP → 再减少 30%800 KB
3. 图片懒加载 → 首屏只加载 3 张图
4. 代码分割 → 首屏 JS 减少到 300 KB
5. 启用 Gzip → CSS 减少到 150 KB
**结果**
- 首屏时间:8 秒 → 1.8 秒(减少 77%)
- Lighthouse 性能评分:35 → 92
### 9.2 案例 2:大型应用的性能优化
**问题**:单页应用(SPA)首次加载慢
**优化策略**
1. **路由懒加载**:每个路由单独打包
2. **组件懒加载**:非首屏组件延迟加载
3. **虚拟列表**:长列表只渲染可见部分
4. **预加载下一页**:用户可能访问的页面预加载
5. **SSR(服务端渲染)**:首屏由服务器渲染
**技术选型**
- 使用 **Vite**(快速构建)
- 使用 **Vue 3**(更好的性能)
- 使用 **Pinia**(轻量状态管理)
- 使用 **Vant**(按需引入的 UI 组件库)
**结果**
- 首屏加载时间:4.5 秒 → 1.2 秒
- Time to Interactive6 秒 → 1.8 秒
### 9.3 案例 3:移动端性能优化
**移动端特殊挑战**
- CPU 性能弱
- 网络慢
- 内存有限
**优化措施**
1. **减少动画**:使用 CSS 动画代替 JS 动画
2. **触摸优化**:避免 `click` 延迟,使用 `touchstart`
3. **减少重排**:使用 `transform` 代替 `top/left`
4. **减少资源**:移动端加载更小的图片
5. **PWA**:支持离线访问,提供类原生体验
**结果**
- 移动端评分:45 → 95
- 用户留存率提升:+30%
---
## 10. 总结与最佳实践
### 10.1 性能优化清单
**加载优化**
- ✅ 启用 Gzip/Brotli 压缩
- ✅ 使用 CDN 加速静态资源
- ✅ 实施代码分割和懒加载
- ✅ 压缩和优化图片
- ✅ 使用 WebP/AVIF 格式
**渲染优化**
- ✅ 减少重排和重绘
- ✅ 使用 CSS 动画(transform、opacity
- ✅ 优化关键渲染路径
- ✅ 内联关键 CSS
**JavaScript 优化**
- ✅ 压缩和 Tree Shaking
- ✅ 避免长任务(时间切片)
- ✅ 使用 Web Workers
- ✅ 防抖和节流
**缓存优化**
- ✅ 配置 HTTP 缓存
- ✅ 使用 Service Worker
- ✅ 合理使用 LocalStorage
**监控优化**
- ✅ 设置性能预算
- ✅ 使用 RUM 和合成监控
- ✅ 定期审计性能
### 10.2 性能优化原则
1. **测量优先**:先测量,再优化
2. **抓大放小**:先优化最大的瓶颈
3. **用户体验第一**:关注真实用户感受
4. **持续改进**:性能优化是持续的过程
5. **团队协作**:让整个团队都关注性能
### 10.3 常见陷阱
- ❌ 过早优化:没有测量就开始优化
- ❌ 过度优化:为了优化而优化,得不偿失
- ❌ 只关注分数:Lighthouse 分数高不代表用户体验好
- ❌ 忽视移动端:移动端性能更重要
- ❌ 一次性优化:性能需要持续监控和改进
### 10.4 学习资源
**工具**
- [Lighthouse](https://developers.google.com/web/tools/lighthouse)
- [WebPageTest](https://www.webpagetest.org)
- [PageSpeed Insights](https://pagespeed.web.dev)
**文档**
- [Web.dev Performance](https://web.dev/performance/)
- [MDN Performance](https://developer.mozilla.org/en-US/docs/Web/Performance)
**书籍**
- 《高性能网站建设指南》
- 《高性能网站建设进阶指南》
- 《Web 性能权威指南》
---
**记住**:性能优化不是炫技,而是为用户创造价值。快的体验就是好的体验。