Appearance
前端性能优化专题
聚合 framework / browser / engineering / css 四处散点,按「渲染层 → 网络层 → 构建层 → 框架层 → CSS 层」组织
一、核心度量指标(Core Web Vitals)
| 指标 | 全称 | 含义 | 优秀标准 |
|---|---|---|---|
| FP | First Paint | 首次绘制像素时间 | — |
| FCP | First Contentful Paint | 首次内容绘制 | < 2s |
| LCP | Largest Contentful Paint | 最大内容绘制 | < 2.5s |
| FID | First Input Delay | 首次用户交互响应延迟 | < 100ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | < 0.1 |
| TTI | Time to Interactive | 完全可交互时间 | — |
长任务:执行时间超过 50ms 的任务。用户交互响应推荐在 100ms 内
二、渲染层优化
源头:浏览器 · 进程与渲染
1. 关键渲染路径(CRP)
- 解析 HTML 构建 DOM 树
- 解析 CSS 生成 CSSOM
- 合并 DOM + CSSOM → Render 树
- 布局(Layout/reflow)→ 绘制(paint)→ 合成(composite)
优化策略:
- 优化 DOM:删除不必要代码/注释/空格,GZIP 压缩
- 优化 CSSOM:减少关键 CSS 体积,非关键 CSS 标记为
media查询 - 优化 JS:消除阻塞渲染的 JS(
async/defer),将非关键 JS 延迟执行 - 优化关键字节:缩小压缩文件减少下载时间
2. 复合层与硬件加速
- 开启硬件加速(
transform: translate3d()、will-change)可将节点提升为独立复合图层 - 复合图层间绘制互不干扰,由 GPU 直接控制
- 仅触发合成的 CSS 属性(不回流不重绘):只有
transform和opacity
内存占用:每个复合层需缓存图像数据(宽 × 高 × 4 字节),不可滥用
动画优化技巧:
- 避免隐式合成:保持动画对象 z-index 尽可能高
- 动画中只使用
transform和opacity will-change慎用,滥用会大幅增加内存
3. 虚拟滚动
- 原理:只渲染可视区域的列表项,滚动时动态更新
startIndex/endIndex - 适用:大量数据列表(1000+ 条),性能提升可达 78%
- 插件:
vue-virtual-scroller(固定行高 RecycleScroller / 动态行高 DynamicScroller)
4. Web Worker 优化长任务
js
// worker.js
onmessage = function(e) {
let sum = e.data
for (let i = 0; i < 200000; i++) { sum += Math.random() }
postMessage(sum)
}
// 主线程
const worker = new Worker('worker.js')
worker.postMessage(0)
worker.onmessage = (e) => console.log(e.data)使用前提:运算时长 - 通信时长 > 50ms 才值得使用
5. 骨架屏
- 白屏原因:CSR 项目等待 JS 加载/CSSOM 构建期间的灰白屏
- 对比 SSR 和预渲染:骨架屏成本最低,适合所有 CSR 页面
- 实现:手动编写 / killblanks 插件(构建时自动生成)/ 封装
<skeleton>组件
三、网络层优化
1. gzip 压缩
- 压缩率通常可达 70%(1.4MB → 573KB)
- Nginx:
gzip on; gzip_types text/css application/javascript application/json; - Node(Express):
app.use(compression()) - Webpack 预压缩:
CompressionWebpackPlugin(threshold: 10KB,ratio: 0.8)
2. CDN 优化(externals)
将大型第三方库(vue/element-ui/axios)通过 CDN 引入,webpack 构建时用 externals 排除打包:
js
externals: {
'vue': 'Vue',
'element-ui': 'ELEMENT',
'axios': 'axios',
}效果:vendor.js 从 959KB → 12KB,加载时间 3s → 1s
3. 前端缓存策略(Vue + Nginx)
| 资源类型 | 缓存策略 | 原因 |
|---|---|---|
| index.html | 不缓存(Cache-Control: no-store) | 入口文件,每次获取最新版本引用 |
| JS/CSS/图片 | 强制缓存(30天) | 文件名含 contenthash,内容变则文件名变 |
4. 预加载策略(Preload / Prefetch)
- preload:告诉浏览器当前页面马上要用到的文件,高优先级下载
- prefetch:告诉浏览器空闲时提前下载用户接下来可能用到的文件
- 策略:preload
chunk-vendors+ 入口 chunk;prefetch 常用 async chunks - 注意:preload 要放在 prefetch 之前;prefetch 不应滥用(占用带宽)
- 如果
<script>有crossorigin属性,prefetch/preload 也必须有,否则资源会重复下载
5. JS 六种加载方式对比
| 方式 | 阻塞 DOM | 执行时机 | 有序性 | 适用场景 |
|---|---|---|---|---|
普通 <script> | 阻塞 | 立即 | 按顺序 | 必须同步的核心脚本 |
async | 不阻塞 | 加载完立即执行 | 无序 | 埋点统计等无依赖脚本 |
defer | 不阻塞 | DOMContentLoaded 前 | 按顺序 | 需控制执行顺序的脚本 |
type="module" | 不阻塞 | 同 defer | 按 import 顺序 | Vite 开发模式 |
preload | 不阻塞 | 预加载(不执行) | — | 当前页关键资源 |
prefetch | 不阻塞 | 空闲时加载 | — | 其他页面所需资源 |
四、构建层优化
1. 分片优化(splitChunks 实战)
分片原则:
node_modules中的文件打包进chunk-vendors,变动频率低,可利用 304 缓存- 被引用少且体量大的库单独分一个 chunk(如 echarts、tinymce)
- 公共代码分成多个 chunk,避免从某入口访问时下载全部公共代码
- 体量很小的异步 chunk 合并进相关 chunk 或入口 chunk
路由懒加载决策矩阵:
| 使用频率高 | 使用频率低 | |
|---|---|---|
| 文件小 | 合并进入口 chunk | 单独一个 chunk |
| 文件大 | 单独一个 chunk(加 prefetch) | 单独一个 chunk |
cacheGroups 配置示例:
js
splitChunks: {
maxInitialRequests: 5,
maxAsyncRequests: 6,
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
minChunks: 2,
chunks: 'all',
},
echarts: {
name: 'chunk-echarts',
test: /[\\/]node_modules[\\/]echarts[\\/]/,
priority: 0,
chunks: 'all',
},
common: {
name: true,
minChunks: 2,
minSize: 60000,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true,
},
},
}chunk-vendors vs chunk-common 策略不同:vendors 是第三方库,变动少可用 304 长期缓存;common 是项目代码,经常变动缓存易失效,因此 common 应拆分而非合并
2. 路由懒加载
js
{
path: '/user/XXX',
component: () => import(/* webpackChunkName: "chunk-user-XXX" */ 'src/pages/XXX'),
}路由懒加载原理(Webpack JSONP 机制)
import() 编译后,Webpack 生成以下核心机制:
window["webpackJsonp"]:全局数组,各 chunk 通过.push()注册自身模块webpackJsonpCallback:拦截 push 方法,将 chunk 中的模块缓存到modules对象,并更新installedChunks[chunkId] = 0(标记已加载)__webpack_require__(moduleId):执行模块,带缓存(installedModules)避免重复执行__webpack_require__.e(chunkId)(核心):通过 JSONP 动态加载 chunk- 创建
<script>标签,src指向对应 chunk 文件 - 三种加载状态:
0(已完成)、undefined(未加载/失败/超时)、Promise(加载中) - 超时处理:120s 超时后 reject Promise,抛出
ChunkLoadError
- 创建
完整流程:
用户访问路由 → import() 触发 → webpackAsyncContext 查 map 找到 chunkId
→ __webpack_require__.e(chunkId) 创建 <script> 加载 JS
→ chunk 加载完成执行 webpackJsonpCallback → 模块注册到 modules
→ __webpack_require__(moduleId) 执行模块 → 渲染页面3. Tree Shaking
- 仅对命名导出(
export function xxx())生效,export default导出对象无法静态分析 - Vue3 全面拥抱函数式编程、按需导入的原因之一
- 前提条件:必须使用 ES 模块规范(静态分析才能确定依赖关系)
4. 组件懒加载
js
// 懒加载 → 独立打包为 dialogInfo.js,点击时才加载
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo')五、框架层优化
源头:框架 · 组件封装
1. Vue 项目性能优化
v-ifvsv-show:频繁切换用 v-show,首次渲染用 v-ifv-for的 key:列表变化时用唯一不变 key 借助本地复用- 多用
computed:依赖不变不重新计算 - 合理使用
destroyed清理事件/定时器;动态组件用keep-alive缓存 - 不需要响应式的数据用
Object.freeze冻结 - 第三方插件按需加载
- 使用运行时版本(vue.runtime.esm.js)比完整版小约 30%
2. SPA 首屏优化
- 减小入口文件体积(splitChunks + 路由懒加载)
- 静态资源本地缓存(浏览器缓存 + HTTP 缓存控制)
- UI 框架按需加载
- 图片资源压缩
- 开启 GZip 压缩
- 使用 SSR
- CDN externals 减小 vendor.js
3. Vue3 大型表格性能优化
场景:20行 × 180列 el-table,switch 切换耗时 7-8s(Vue3 响应式开销)
优化手段:
shallowRef 替代 ref(减少 17-20%)
ref()深层递归代理所有属性,shallowRef()仅代理.value本身
jsconst data = shallowRef([]) // 仅 data.value = xxx 触发更新渲染函数中避免响应式依赖(减少 7-20%)
js// 提取为普通数组,避免重复代理 const plainCols = columns.value.map(item => ({ realWidth: item.realWidth }))v-if 替代 disabled(减少 80%)
- el-tooltip 等复杂组件会成倍增加 patch 比对耗时
html<!-- 仅需要时才创建 tooltip 组件 --> <span v-if="!showTooltip">{{ text }}</span> <el-tooltip v-else>...</el-tooltip>
六、CSS 层优化
源头:CSS · 工程化
1. 选择器匹配机制(从右向左)
- 浏览器解析选择器时从右向左匹配
- 最右侧部分决定匹配效率,应尽可能精确
2. 加载性能
- CSS 压缩、减少
@import(用link代替) - 避免通配符选择器
* - 选择器层级不超过三层,多用 class 少用标签
3. 渲染性能
- 减少重排重绘(避免频繁读取
offset*/scroll*/client*) - 去除空规则、属性值 0 不加单位
- CSS 雪碧图(小图标)
- 减少昂贵属性:
box-shadow、border-radius、filter、:nth-child will-change提前告知浏览器(不可滥用)