Files
test-repo/docs/zh-cn/appendix/frontend-performance.md
T
sanbuphy e8bba6f7c0 feat(docs): add performance overview demo component and update content structure
- Create PerformanceOverviewDemo.vue with interactive performance dimension visualization
- Update config.mjs to support new component registration
- Add new frontend evolution components to theme/index.js
- Consolidate stage-0 intro pages into index.md across all locales
- Enhance LLM intro documentation with tokenization details
2026-02-05 01:33:28 +08:00

560 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端性能优化 (Frontend Performance)
> 💡 **学习指南**:本章节无需深入的算法背景,通过交互式演示带你掌握前端性能优化的核心逻辑。我们将从最直观的页面加载讲起,一直到浏览器底层的渲染机制和缓存策略。
<PerformanceOverviewDemo />
<PerformanceMetricsDemo />
## 0. 引言:从 "能用" 到 "好用"
如果把访问网页比作去餐厅**吃饭**,那么:
- **加载 (Loading)** 就是食材(HTML/CSS/JS/图片)从仓库(服务器)运送到厨房(浏览器)的过程。
- **渲染 (Rendering)** 就是厨师(浏览器引擎)把食材加工成美味菜肴(页面)的过程。
- **交互 (Interaction)** 就是服务员响应你的需求(点击、滚动)。
**前端性能优化**的本质,就是为了让这三个过程**更快、更顺畅**
它的核心任务只有一个:**最大限度地减少用户的等待时间。**
为了实现这个目标,我们需要解决三个核心挑战:
1. **传输**:怎么把“食材”运得更快?(压缩、CDN、懒加载)
2. **渲染**:怎么让“厨师”做得更快?(关键渲染路径、重排重绘)
3. **记忆**:怎么避免重复劳动?(缓存策略)
本教程将带你一步步拆解这些优化技巧。
---
## 1. 第一步:传输 (Loading)
在厨师开始做饭之前,首先得有食材。如果运送食材的卡车堵在路上了,厨房里再厉害的大厨也得干瞪眼。
### 1.1 为什么网速快了,网页还是很慢?
你可能会疑惑:现在的 5G 和光纤这么快,为什么有些网页打开还是很慢?
原因通常有两个:
1. **东西太多**:一张高清大图可能就有 5MB,相当于下载一本书。
2. **路太堵**:浏览器同时下载的资源数量是有限的(通常 6 个),就像只有 6 辆小卡车在运货,多出来的得排队。
### 1.2 解决方案:瘦身与偷懒
为了解决这个问题,我们主要用两招:**压缩(瘦身)**和**懒加载(偷懒)**。
#### 瘦身:图片与代码压缩
图片通常是网页里最大的“胖子”。
现代的图片格式(如 WebP, AVIF)就像是采用了高科技压缩技术的压缩包,在画质几乎不变的情况下,体积能减小 30%-70%。
<ImageOptimizationDemo />
#### 偷懒:懒加载 (Lazy Loading)
“偷懒”在这里是个褒义词。
如果用户只在看第一屏的内容,为什么要把底下第十屏的图片也下载下来呢?
**懒加载**的策略是:**只加载用户看得到的内容**。当用户滚动页面,图片快要出现时,再去下载它。
<LazyLoadingDemo />
**关键点**:永远不要让用户下载他们不需要(或者暂时不需要)的资源。
---
## 2. 核心难题:渲染 (Rendering)
食材运到了,接下来压力给到了厨师(浏览器)。
### 2.1 浏览器的“单线程”困境
浏览器里的大厨(主线程)非常忙,他不仅要负责**画页面**(布局、绘制),还要负责**响应用户**(点击事件、JS 逻辑)。
最糟糕的是,他只有**一个人**(单线程)。
如果你让他在切菜(运行复杂的 JS 计算)的时候,顾客(用户)想点菜(点击按钮),他是没法理你的。这就导致了**卡顿**。
### 2.2 关键渲染路径 (Critical Rendering Path)
为了让用户尽快看到东西,浏览器制定了一套标准的工作流程,我们叫它**关键渲染路径**:
1. **HTML -> DOM**:把菜谱读懂,列出食材清单。
2. **CSS -> CSSOM**:搞清楚每种食材怎么处理(颜色、大小)。
3. **Render Tree**:把清单和处理方法结合,决定最后上桌的菜。
4. **Layout (排版)**:决定每个菜摆在盘子的哪个位置。
5. **Paint (绘制)**:最后淋上酱汁,上色。
<CriticalRenderingPathDemo />
### 2.3 避坑指南:重排 (Reflow) 与重绘 (Repaint)
在这个流程中,最累人的步骤是 **Layout (排版)**
- **重排 (Reflow)**:如果你改变了元素的大小或位置,浏览器通过重新计算所有元素的位置。这就像因为桌子移了一下,整个餐厅的椅子都要重新摆一遍。**非常消耗性能!**
- **重绘 (Repaint)**:如果你只是改变了颜色,浏览器只需要重新上色。这就像给桌布换个颜色,简单多了。
<ReflowRepaintDemo />
**优化原则**
- 尽量避免**重排**(比如不要频繁修改 `width`, `top`)。
- 尽量使用只会触发**合成**(Composite)的属性(如 `transform`, `opacity`),这相当于让 GPU(帮厨)来干活,不占用主厨的时间。
---
## 3. 进阶:处理海量数据
如果你的网页需要展示 10,000 条聊天记录,或者 5,000 个商品列表,该怎么办?
### 3.1 为什么不能直接 `v-for`
如果直接在页面上生成 10,000 个 `<div>`,浏览器的内存会瞬间爆炸,渲染树会变得巨大无比,每动一下都会卡死。
这就好比餐厅里只有 10 张桌子,你却非要一次性接待 10,000 个客人,结果就是谁也吃不上饭。
### 3.2 解决方案:虚拟列表 (Virtual Scrolling)
聪明的工程师想出了**虚拟列表**。
它的核心思想是:**欺骗眼睛**。
既然屏幕只能显示 10 条数据,那我就只渲染这 10 条(加上前后一点缓冲)。当用户滚动时,我快速地把移出屏幕的 DOM 销毁,把新进入屏幕的数据填进去。
用户感觉他在滚一个无限长的列表,但实际上浏览器里永远只有几十个 DOM 节点。
<VirtualScrollingDemo />
**关键点**DOM 节点是昂贵的,能省则省。
---
## 4. 脚本执行优化 (Script Execution)
JavaScript 的执行是阻塞主线程的,优化 JS 执行效率对于保持页面流畅至关重要。
### 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.3 压缩与裁剪
**使用工具压缩图片**
- **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:新闻列表页优化
**问题**:首屏加载慢,滚动卡顿
**优化**
1. **图片**WebP + 懒加载
2. **列表**:虚拟列表(只渲染可见的 10 项)
3. **数据**:分页加载
**结果**LCP 2.5s -> 0.8s
### 9.2 案例 2:数据可视化大屏
**问题**:渲染大量节点卡死
**优化**
1. **渲染**Canvas 代替 DOM
2. **计算**Web Worker 处理数据
**结果**FPS 10 -> 60
### 9.3 案例 3:移动端活动页
**问题**:白屏时间长
**优化**
1. **资源**:预加载 (Preload) 关键图
2. **体验**:骨架屏 (Skeleton)
**结果**:白屏减少 60%
---
## 10. 总结与最佳实践
### 10.1 性能优化清单
**加载优化**
- ✅ 启用 Gzip/Brotli 压缩
- ✅ 使用 CDN 加速静态资源
- ✅ 实施代码分割和懒加载
- ✅ 压缩和优化图片
**渲染优化**
- ✅ 减少重排和重绘
- ✅ 优化关键渲染路径
- ✅ 使用 CSS 动画代替 JS 动画
**执行优化**
- ✅ 使用 Web Workers 处理重计算
- ✅ 避免长任务(Long Tasks
- ✅ 合理使用防抖和节流
**缓存优化**
- ✅ 配置 HTTP 强缓存和协商缓存
- ✅ 考虑使用 Service Worker
### 10.2 持续学习
前端性能优化是一个不断发展的领域,新的标准(如 INP)和新的工具(如 Vite, Turbopack)层出不穷。保持好奇心,多看 Performance 面板,是你最好的老师。
---
## 11. 名词速查表 (Glossary)
| 名词 | 全称 | 解释 |
| :--- | :--- | :--- |
| **FP / FCP** | First Paint / First Contentful Paint | **首屏时间**。用户看到页面第一个像素/第一块内容的时间。 |
| **LCP** | Largest Contentful Paint | **最大内容绘制**。页面主要内容加载完成的时间(衡量加载速度的核心指标)。 |
| **INP** | Interaction to Next Paint | **交互到下一次绘制**。衡量页面响应速度的新指标(替代 FID),关注点击后的反馈延迟。 |
| **CLS** | Cumulative Layout Shift | **累积布局偏移**。页面加载时元素乱跳的程度(衡量视觉稳定性)。 |
| **TTFB** | Time to First Byte | **首字节时间**。从发出请求到接收到服务器第一个字节的时间(衡量后端响应速度)。 |
| **TBT** | Total Blocking Time | **总阻塞时间**。主线程被长任务阻塞的总时间(衡量页面交互流畅度)。 |
| **Reflow** | Reflow (Layout) | **重排**。浏览器重新计算元素位置和大小的过程。成本高,应避免。 |
| **Repaint** | Repaint | **重绘**。浏览器重新绘制元素外观(如颜色)的过程。成本中等。 |
| **CDN** | Content Delivery Network | **内容分发网络**。把文件存在离用户最近的服务器上,加速下载。 |
| **SSR** | Server-Side Rendering | **服务端渲染**。在服务器端生成 HTML,加快首屏显示,利于 SEO。 |
| **CSR** | Client-Side Rendering | **客户端渲染**。在浏览器端通过 JS 生成 HTML,交互体验好,但首屏慢。 |
| **SSG** | Static Site Generation | **静态站点生成**。构建时生成静态 HTML,访问速度极快。 |
| **Tree Shaking** | Tree Shaking | **摇树优化**。构建时移除未使用的代码,减小包体积。 |
| **Code Splitting** | Code Splitting | **代码分割**。将代码拆分成小块,按需加载。 |
| **Preload / Prefetch** | Preload / Prefetch | **预加载/预获取**。提前告知浏览器加载关键资源或未来可能用到的资源。 |