Skip to main content

性能优化

性能优化 = 度量 + 策略 + 验证。常见误区:上来就上手段,不量数据。正确姿势:先量基线 → 找瓶颈 → 优化 → 验证 → 沉淀

优化地图

性能优化
├─ 构建时(开发者体验 + 产物大小)
│ ├─ 构建速度
│ ├─ 产物体积
│ └─ 缓存策略
├─ 资源加载(首屏)
│ ├─ 网络层(CDN / HTTP/2 / 预连接)
│ ├─ 资源大小(压缩 / 格式 / 按需)
│ └─ 加载顺序(preload / prefetch / 关键 CSS)
└─ 运行时(交互体验)
├─ 渲染性能(长任务 / 重排重绘 / 虚拟列表)
├─ JS 性能(防抖节流 / Web Worker / 算法)
└─ 体验优化(骨架屏 / 懒加载 / SSR)

一、构建速度

1. 通用方案

手段效果适用
持久化缓存二次构建提速 50%+通用
多核并行(thread-loader)大型项目显著webpack4/5
替换为更快的工具量级提升通用
// webpack5 cache
module.exports = {
cache: { type: "filesystem" },
};

// 用 swc/esbuild 替换 babel/tsc
// 速度对比:babel > swc/esbuild(5~10x)

2. 减小 loader 处理范围

module: {
rules: [
{
test: /\.js$/,
// 关键:跳过 node_modules
exclude: /node_modules/,
use: "swc-loader",
},
],
}

3. 升级到 Vite(如果可以)

  • 开发用 esbuild + 原生 ESM,启动 < 1s
  • 生产用 Rollup,产物质量有保障

二、产物体积

1. 分析工具

# webpack
pnpm i -D webpack-bundle-analyzer

# vite
pnpm i -D rollup-plugin-visualizer

2. 常用手段

  • 代码分割:路由级懒加载 + 第三方库抽离
splitChunks: {
chunks: "all",
cacheGroups: {
vendors: { test: /[\\/]node_modules[\\/]/, name: "vendors", priority: 10 },
},
}
  • Tree Shaking:使用 ES Module + sideEffects: false
  • 按需引入:UI 库按需加载(babel-plugin-importunplugin-vue-components
  • 图片优化:压缩、WebP/AVIF、图标用 SVG sprite
  • 第三方库瘦身:用 lodash-es 替代 lodashdayjs 替代 moment

3. Gzip / Brotli

构建时生成 .gz / .br,nginx 配合 gzip_static on; 直接读预压缩文件,省 CPU。

三、资源加载

1. 关键资源预加载

<!-- 关键字体 -->
<link rel="preload" href="/font.woff2" as="font" crossorigin>

<!-- 下一步路由的 chunk -->
<link rel="prefetch" href="/static/js/user-page.js">

<!-- 跨域关键资源 -->
<link rel="preconnect" href="https://api.example.com">

2. 关键 CSS 内联

首屏关键 CSS 内联到 <style>,非关键 CSS 异步加载:

<style>/* critical CSS 内联 */</style>
<link rel="preload" href="/static/css/main.css" as="style" onload="this.rel='stylesheet'">

3. 图片懒加载

<img loading="lazy" decoding="async" src="..." />

JS 用 IntersectionObserver 实现自定义懒加载。

四、运行时性能

1. 减少重排重绘

// ❌ 多次写入,多次重排
el.style.width = "100px";
el.style.height = "100px";
el.style.left = "10px";

// ✅ 一次性 class 切换,只触发一次
el.classList.add("box");

// ✅ 离线 DOM
const frag = document.createDocumentFragment();
// ... 批量操作
container.appendChild(frag);

2. 长列表虚拟滚动

只渲染可视区内的 DOM 节点。开源方案:react-windowvue-virtual-scroller@tanstack/virtual

3. 大数据渲染分片

// requestIdleCallback 切片渲染
function renderInChunks(list: any[], renderItem: (item: any) => void) {
let i = 0;
function step() {
const start = performance.now();
while (i < list.length && performance.now() - start < 5) {
renderItem(list[i++]);
}
if (i < list.length) requestIdleCallback(step);
}
requestIdleCallback(step);
}

4. Web Worker

把 CPU 密集型任务(解析、加密、图像处理)丢到 Worker:

// main.ts
const worker = new Worker(new URL("./heavy.worker.ts", import.meta.url), { type: "module" });
worker.postMessage(data);
worker.onmessage = (e) => console.log(e.data);

五、缓存策略

1. HTTP 缓存

HTML:    Cache-Control: no-cache     # 每次协商缓存
静态资源: Cache-Control: public, max-age=31536000, immutable # 文件名带 hash

2. Service Worker(PWA)

// workbox
import { precacheAndRoute } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { CacheFirst } from "workbox-strategies";

precacheAndRoute(self.__WB_MANIFEST);
registerRoute(({ request }) => request.destination === "image", new CacheFirst());

3. 接口缓存

  • 不可变数据:SWR 风格的 stale-while-revalidate
  • 频繁读、偶尔写:localStorage / IndexedDB
  • 实时性要求高:放弃前端缓存

六、面试高频

  • Q:首屏优化从哪些方面入手? A:网络(CDN、压缩、预连接)、资源(按需、图片优化、字体子集)、加载顺序(preload、关键 CSS 内联)、JS 体积(splitChunks、按需引入)、运行时(SSR、懒加载)。
  • Q:长列表卡顿怎么优化? A:虚拟滚动(只渲染可视区)、分片渲染(requestIdleCallback / setTimeout)、Web Worker、避免内联函数和复杂对象。
  • Q:Tree Shaking 失效的常见原因? A:使用了 CJS、side effects 标注错误(package.json 缺 sideEffects: false 或误标)、按需引入未生效。

七、推荐资源