前端性能优化实战
记录一下前端性能优化实战,dailyuse(memoflow) web 端。当前包含首包拆分。
#type / howto
#status / growing
#tech / dev / frontend
[!info] related notes
- 相关笔记:
前端性能优化实战
目标
系统性优化 memoflow Web 端的前端性能,提升用户体验。重点关注:
- 首屏加载速度(LCP < 2.5s)
- 交互响应速度(FID < 100ms)
- 视觉稳定性(CLS < 0.1)
前置条件
- 已有可运行的前端应用
- 了解浏览器渲染流程(解析 → 构建 DOM/CSSOM → 布局 → 绘制)
- 了解 Chrome DevTools 的 Performance 和 Lighthouse 面板
- 项目使用 Vue3 / React + Vite 或类似构建工具
核心指标
| 指标 | 全称 | 含义 | 目标 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容绘制时间 | < 2.5s |
| FID | First Input Delay | 首次输入延迟 | < 100ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | < 0.1 |
| TTFB | Time to First Byte | 首字节到达时间 | < 800ms |
| FCP | First Contentful Paint | 首次内容绘制 | < 1.8s |
优化策略分层
┌─────────────────────────────────┐
│ 交互层 (Interaction) │ ← 事件处理、状态更新、虚拟列表
├─────────────────────────────────┤
│ 渲染层 (Rendering) │ ← 减少重排重绘、CSS 优化、动画
├─────────────────────────────────┤
│ 网络层 (Network) │ ← 代码分割、缓存、CDN、压缩
└─────────────────────────────────┘
步骤
网络层优化
1. 代码分割(Code Splitting)
使用动态 import() 实现路由级懒加载:
// router/index.ts
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue'), // 懒加载
},
{
path: '/settings',
component: () => import('@/views/Settings.vue'),
},
]
Vite 中手动分割 chunk:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-vue': ['vue', 'vue-router', 'pinia'],
'vendor-ui': ['element-plus'],
'vendor-echarts': ['echarts'],
},
},
},
},
})
2. 图片优化
- 使用 WebP / AVIF 格式替代 PNG/JPG
- 实现图片懒加载(
loading="lazy"或 Intersection Observer) - 使用
<picture>标签做响应式图片 - 小图标使用 SVG sprite 或 icon font
<!-- 响应式图片示例 -->
<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="hero" loading="lazy" />
</picture>
3. 缓存策略
// vite.config.ts
export default defineConfig({
build: {
// 内容哈希文件名,支持长期缓存
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
},
})
Nginx 缓存配置:
# 静态资源长期缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 不缓存,保证版本更新
location / {
expires -1;
add_header Cache-Control "no-cache";
}
4. Gzip / Brotli 压缩
// vite.config.ts
import compress from 'vite-plugin-compression'
export default defineConfig({
plugins: [
compress({ algorithm: 'gzip' }),
compress({ algorithm: 'brotliCompress' }),
],
})
渲染层优化
5. 减少重排重绘
- 批量 DOM 操作(使用
DocumentFragment或requestAnimationFrame) - 避免频繁读取布局属性(
offsetWidth、scrollTop等触发强制同步布局) - 使用
transform和opacity做动画(仅触发合成层,不触发重排)
6. CSS 优化
- 避免深层嵌套选择器
- 使用
contain: layout限制重排范围 - 关键 CSS 内联,非关键 CSS 异步加载
交互层优化
7. 虚拟列表
长列表使用虚拟滚动,只渲染可视区域内的元素:
// 使用 vue-virtual-scroller
<virtual-scroller :items="largeList" :item-height="50">
<template #default="{ item }">
<div class="list-item">{{ item.name }}</div>
</template>
</virtual-scroller>
8. 防抖与节流
// 搜索输入防抖
import { useDebounceFn } from '@vueuse/core'
const debouncedSearch = useDebounceFn((keyword: string) => {
fetchSearchResults(keyword)
}, 300)
// 滚动事件节流
import { useThrottleFn } from '@vueuse/core'
const throttledScroll = useThrottleFn(() => {
updateScrollPosition()
}, 100)
9. Web Worker 分担计算
将密集计算移到 Worker 线程,避免阻塞主线程:
// worker.ts
self.onmessage = (e) => {
const result = heavyComputation(e.data)
self.postMessage(result)
}
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url))
worker.postMessage(data)
worker.onmessage = (e) => {
// 处理结果
}
验证方式
Lighthouse 审计
# CLI 方式
npx lighthouse https://your-app.com --output html --output-path ./report.html
# Chrome DevTools → Lighthouse tab
Performance 面板
- 打开 Chrome DevTools → Performance
- 点击录制,执行关键操作
- 分析火焰图中的长任务(> 50ms)
- 关注 Main 线程的阻塞情况
Web Vitals 监控
import { onLCP, onFID, onCLS } from 'web-vitals'
onLCP(console.log)
onFID(console.log)
onCLS(console.log)
Bundle 分析
npx vite-bundle-visualizer
# 或
npx source-map-explorer dist/assets/*.js
常见问题
Q: 代码分割后首屏反而变慢?
分割点过多会增加 HTTP 请求数。合并小 chunk,确保每个 chunk > 20KB(否则 gzip 后收益不大)。
Q: 图片懒加载导致 CLS 升高?
为图片预留固定尺寸(width + height 属性或 aspect-ratio CSS),避免加载时布局跳动。
Q: 虚拟列表出现白屏闪烁?
确保 item-height 设置正确。对于不定高的列表,使用动态测量 + 缓存高度的方案。
Q: 如何判断优化是否有效?
对比优化前后的 Lighthouse 分数和 Web Vitals 数据。建议在 CI 中集成 Lighthouse CI 持续监控。