前端性能调优实战指南 — 22 条优化策略

前端性能调优实战指南 --- 22 条优化策略


1. React.memo 避免无效重渲染

分类:React / 渲染优化

优化前

  • 父组件 state 变化,200 个子组件全部重渲染
  • Profiler 单次渲染耗时 120ms
  • 搜索输入有明显卡顿
tsx 复制代码
// 每次父组件 re-render,RowItem 都会重新渲染
function RowItem({ data }: { data: RowData }) {
  return <div>{data.name} - {data.value}</div>;
}

优化后

  • React.memo 包裹子组件,props 不变则跳过渲染
  • 渲染耗时降至 8ms
  • 搜索输入丝滑流畅
tsx 复制代码
const RowItem = React.memo(function RowItem({ data }: { data: RowData }) {
  return <div>{data.name} - {data.value}</div>;
});

const filteredList = useMemo(
  () => list.filter(item => item.name.includes(keyword)),
  [list, keyword]
);

对比图

xychart-beta title "渲染耗时对比" x-axis ["优化前", "优化后"] y-axis "耗时 (ms)" 0 --> 130 bar [120, 8]
指标 优化前 优化后 变化
渲染耗时 120ms 8ms 减少了 93%
重渲染组件数 200 个 5 个 减少了 96%

2. 虚拟滚动长列表

分类:DOM 优化

优化前

  • 5000+ 行数据全量 DOM 渲染
  • DOM 节点数 15,000+
  • 首屏渲染 3.2s ,滚动仅 12fps
tsx 复制代码
function DataTable({ rows }: { rows: DataRow[] }) {
  return (
    <div className="table-body">
      {rows.map(row => (
        <div key={row.id} className="table-row">
          <span>{row.name}</span>
          <span>{row.status}</span>
        </div>
      ))}
    </div>
  );
}

优化后

  • react-window 只渲染可视区域内的行
  • DOM 节点数 ~60
  • 首屏 200ms ,滚动稳定 60fps
tsx 复制代码
import { FixedSizeList } from 'react-window';

function DataTable({ rows }: { rows: DataRow[] }) {
  return (
    <FixedSizeList height={600} itemCount={rows.length} itemSize={48} width="100%">
      {({ index, style }) => (
        <div style={style} className="table-row">
          <span>{rows[index].name}</span>
          <span>{rows[index].status}</span>
        </div>
      )}
    </FixedSizeList>
  );
}

对比图

xychart-beta title "DOM 节点数对比" x-axis ["优化前", "优化后"] y-axis "节点数" 0 --> 16000 bar [15000, 60]
xychart-beta title "滚动帧率对比 (fps)" x-axis ["优化前", "优化后"] y-axis "fps" 0 --> 65 bar [12, 60]
指标 优化前 优化后 变化
DOM 节点 15,000+ ~60 减少了 99.6%
首屏渲染 3.2s 200ms 减少了 94%
滚动帧率 12fps 60fps 提升了 5 倍

3. 路由级代码分割

分类:构建优化

优化前

  • 所有页面打包为单个 main.js
  • 首屏 JS 体积 860KB (gzip)
  • 3G 网络首屏白屏 4.5s
tsx 复制代码
import HomePage from './pages/home';
import DashboardPage from './pages/dashboard';
import SettingsPage from './pages/settings';

优化后

  • React.lazy 按路由动态导入,按需加载
  • 首屏 JS 体积 210KB
  • 首屏加载 1.8s
tsx 复制代码
const HomePage = lazy(() => import('./pages/home'));
const DashboardPage = lazy(() => import('./pages/dashboard'));
const SettingsPage = lazy(() => import('./pages/settings'));

<Suspense fallback={<PageSkeleton />}>
  <HomePage />
</Suspense>

对比图

xychart-beta title "首屏 JS 体积对比 (KB, gzip)" x-axis ["优化前", "优化后"] y-axis "体积 (KB)" 0 --> 900 bar [860, 210]
gantt title 资源加载瀑布流对比 dateFormat X axisFormat %s section 优化前 main.js 860KB (阻塞 4.5s) :crit, 0, 45 section 优化后 vendor.js 120KB :active, 0, 12 home.js 90KB :active, 0, 9 dashboard.js (按需) :done, 18, 18
指标 优化前 优化后 变化
首屏 JS 860KB 210KB 减少了 76%
首屏加载时间 4.5s 1.8s 减少了 60%

4. 接口防抖 + 缓存

分类:网络请求 / 缓存策略

优化前

  • 每次键入字符立即请求 API
  • 输入 11 字符触发 11 次请求
  • 服务端峰值 800 QPS/min
tsx 复制代码
const handleChange = async (e) => {
  const value = e.target.value;
  setKeyword(value);
  const res = await fetchSearchResults(value); // 每次输入都请求
  setResults(res.data);
};

优化后

  • 300ms 防抖 + React Query 5 分钟缓存
  • 同样输入仅 1-2 次请求
  • 服务端降至 80 QPS/min
tsx 复制代码
const debouncedKeyword = useDebouncedValue(keyword, 300);

const { data: results = [] } = useQuery({
  queryKey: ['search', debouncedKeyword],
  queryFn: () => fetchSearchResults(debouncedKeyword),
  enabled: debouncedKeyword.length > 0,
  staleTime: 5 * 60 * 1000,
});

对比图

xychart-beta title "输入 performance 触发的 API 请求数" x-axis ["优化前", "优化后"] y-axis "请求数" 0 --> 12 bar [11, 2]
指标 优化前 优化后 变化
请求数 11 次 1-2 次 减少了 90%
服务端 QPS 800/min 80/min 减少了 90%
缓存命中延迟 200ms 0ms 减少了 100%

5. 图片 WebP + 懒加载

分类:资源优化

优化前

  • 20 张 PNG Banner,均值 350KB/张
  • 总体积 7MB,首屏一次性加载
  • LCP 5.8s
html 复制代码
<img src="/images/banner1.png" />
<!-- 20 张全部在首屏加载 -->

优化后

  • WebP 格式 + loading="lazy" 原生懒加载
  • 均值 85KB/张,首屏仅加载 3 张
  • LCP 2.1s
tsx 复制代码
<picture>
  <source srcSet={banner.webpUrl} type="image/webp" />
  <img src={banner.pngUrl} loading="lazy" decoding="async" />
</picture>

对比图

pie title 优化前图片体积分布 "首屏加载 (3张)" : 1050 "其余加载 (17张)" : 5950
pie title 优化后图片体积分布 "首屏加载 (3张)" : 255 "懒加载 (17张)" : 1445 "体积节省" : 5300
指标 优化前 优化后 变化
图片体积 7MB 1.7MB 减少了 76%
LCP 5.8s 2.1s 减少了 64%

6. useMemo 缓存昂贵计算

分类:React / 计算优化

优化前

  • 10000 条数据每次渲染都重新过滤、分组、聚合
  • 计算耗时 65ms
  • 切换 Tab 有明显延迟
tsx 复制代码
function Dashboard({ rawData, activeTab }: Props) {
  const chartData = processRawData(rawData);   // 每次渲染都执行 45ms
  const summary = calculateSummary(rawData);    // 每次渲染都执行 20ms
}

优化后

  • useMemo 缓存,仅在 rawData 变化时重新计算
  • 缓存命中耗时 0.1ms
  • 切换 Tab 即时响应
tsx 复制代码
const chartData = useMemo(() => processRawData(rawData), [rawData]);
const summary = useMemo(() => calculateSummary(rawData), [rawData]);

对比图

xychart-beta title "切换 Tab 时计算耗时 (ms)" x-axis ["优化前", "优化后"] y-axis "耗时 (ms)" 0 --> 70 bar [65, 0.1]
指标 优化前 优化后 变化
计算耗时 65ms 0.1ms 减少了 99%

7. Tree Shaking 按需导入

分类:构建优化 / 依赖瘦身

优化前

  • lodash 全量导入 + moment.js
  • 仅用 3 个函数,却引入 530KB
  • 占总 bundle 35%
tsx 复制代码
import _ from 'lodash';
import moment from 'moment';

优化后

  • lodash 路径导入 + dayjs 替代 moment
  • 两库合计 28KB
  • 占总 bundle 2.5%
tsx 复制代码
import groupBy from 'lodash/groupBy';
import sortBy from 'lodash/sortBy';
import dayjs from 'dayjs';

对比图

pie title 优化前 Bundle 组成 "lodash + moment" : 530 "其他依赖" : 985
pie title 优化后 Bundle 组成 "lodash/* + dayjs" : 28 "其他依赖" : 985
指标 优化前 优化后 变化
依赖体积 530KB 28KB 减少了 95%
占 bundle 比例 35% 2.5% 减少了 93%

8. 状态管理下沉

分类:React / 状态管理

优化前

  • 表单每个字段的临时值都存 Redux 全局 store
  • 输入触发 47 个 组件重渲染(含导航栏、侧边栏)
  • 输入延迟 80ms,打字卡顿
tsx 复制代码
const value = useSelector((state) => state.form[fieldName]);
const dispatch = useDispatch();
<input value={value} onChange={e => dispatch(updateFormField(fieldName, e.target.value))} />

优化后

  • 表单状态下沉到局部 useState,提交时才同步到全局
  • 3 个 组件重渲染
  • 输入延迟 4ms
tsx 复制代码
const [formData, setFormData] = useState<FormData>(initialData);
const handleSubmit = () => dispatch(submitForm(formData));

对比图

xychart-beta title "单次输入触发的组件渲染数" x-axis ["优化前 (Redux)", "优化后 (useState)"] y-axis "组件数" 0 --> 50 bar [47, 3]
指标 优化前 优化后 变化
渲染组件数 47 3 减少了 94%
输入延迟 80ms 4ms 减少了 95%

9. CSS transform 替代位置属性动画

分类:CSS / 动画流畅度

优化前

  • 抽屉展开/收起使用 left 属性做动画
  • 每帧触发 Layout → Paint → Composite(三阶段全走)
  • 帧率 24fps ,CPU 65%
less 复制代码
.drawer {
  left: -400px;
  transition: left 0.3s ease;
  &.open { left: 0; } // 触发 Layout 重排
}

优化后

  • 改用 transform: translateX(),仅触发 Composite
  • 帧率 60fps ,CPU 12%
less 复制代码
.drawer {
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  will-change: transform;
  &.open { transform: translateX(0); } // 仅触发 Composite
}

对比图

graph LR subgraph 优化前 - left 属性 A1[Layout 重排] --> A2[Paint 重绘] --> A3[Composite 合成] end subgraph 优化后 - transform B1[Composite 合成] end style A1 fill:#e74c3c,color:#fff style A2 fill:#e67e22,color:#fff style A3 fill:#f1c40f,color:#333 style B1 fill:#27ae60,color:#fff
xychart-beta title "动画帧率对比 (fps)" x-axis ["优化前 (left)", "优化后 (transform)"] y-axis "fps" 0 --> 65 bar [24, 60]
指标 优化前 优化后 变化
帧率 24fps 60fps 提升了 150%
CPU 占用 65% 12% 减少了 82%

10. 事件委托替代逐项绑定

分类:JavaScript / 内存优化

优化前

  • 500 行 × 3 按钮 = 1500 个事件监听器
  • 初始化耗时 90ms ,内存多占 12MB
tsx 复制代码
{items.map(item => (
  <div key={item.id}>
    <button onClick={() => handleView(item.id)}>查看</button>
    <button onClick={() => handleEdit(item.id)}>编辑</button>
    <button onClick={() => handleDelete(item.id)}>删除</button>
  </div>
))}

优化后

  • 父容器事件委托,通过 data-* 识别目标
  • 1 个监听器,初始化 0.5ms
tsx 复制代码
<div onClick={handleAction}>
  {items.map(item => (
    <div key={item.id}>
      <button data-action="view" data-id={item.id}>查看</button>
      <button data-action="edit" data-id={item.id}>编辑</button>
      <button data-action="delete" data-id={item.id}>删除</button>
    </div>
  ))}
</div>

对比图

xychart-beta title "事件监听器数量" x-axis ["优化前 (逐项绑定)", "优化后 (事件委托)"] y-axis "监听器数量" 0 --> 1600 bar [1500, 1]
指标 优化前 优化后 变化
监听器数 1,500 1 减少了 99.9%
初始化耗时 90ms 0.5ms 减少了 99%
内存占用 12MB ~1MB 减少了 92%

11. Web Worker 卸载 CPU 密集型任务

分类:JavaScript / 用户体验

优化前

  • 50000 行 Excel 在主线程解析、校验、转换
  • 主线程阻塞 3.8s,页面完全冻结
tsx 复制代码
const handleFileUpload = async (file: File) => {
  const rawData = await readExcelFile(file);       // 800ms
  const validated = validateRows(rawData);           // 1500ms
  const transformed = transformData(validated);      // 1500ms --- 期间页面冻结
};

优化后

  • 计算逻辑移至 Web Worker,主线程始终可交互
  • 主线程阻塞 0ms,有实时进度条
tsx 复制代码
// worker.ts
self.onmessage = async (e) => {
  const validated = validateRows(e.data.rawData);
  self.postMessage({ type: 'progress', value: 50 });
  const transformed = transformData(validated);
  self.postMessage({ type: 'result', data: transformed });
};

对比图

gantt title 主线程占用时间线 dateFormat X axisFormat %s section 优化前 主线程阻塞 3.8s (页面冻结) :crit, 0, 38 section 优化后 主线程空闲 (可正常交互) :active, 0, 38 Worker 处理数据 :done, 0, 32
指标 优化前 优化后 变化
主线程阻塞 3.8s 0ms 减少了 100%
处理速度 3.8s 3.2s 提升了 15%

12. HTTP 请求合并

分类:网络请求 / 首屏体验

优化前

  • 看板初始化 8 个独立 API,受浏览器并发限制分 2 批
  • 瀑布流总耗时 1200ms
  • HTTP 头冗余 16KB
tsx 复制代码
const [sales, orders, users, ...] = await Promise.all([
  fetch('/api/dashboard/sales'),
  fetch('/api/dashboard/orders'),
  // ...共 8 个请求
]);

优化后

  • 后端提供批量接口,1 次请求返回全部数据
  • 总耗时 350ms ,头开销 2KB
tsx 复制代码
const data = await fetch('/api/dashboard/batch', {
  method: 'POST',
  body: JSON.stringify({ modules: ['sales', 'orders', 'users', ...] }),
});

对比图

gantt title 请求瀑布流对比 dateFormat X axisFormat %s section 优化前 (8个请求) sales :crit, 0, 15 orders :crit, 0, 18 users :crit, 0, 12 inventory :crit, 0, 20 reviews :crit, 0, 16 traffic :crit, 20, 38 returns :crit, 20, 33 conversion:crit, 20, 36 section 优化后 (1个请求) /batch :active, 0, 10
指标 优化前 优化后 变化
请求数 8 1 减少了 87.5%
加载时间 1200ms 350ms 减少了 71%

13. 大文件分片上传

分类:文件上传 / 网络优化

优化前

  • 500MB 视频文件单次 POST 直传
  • 上传成功率仅 62%
  • 断网后需从头重传全部 500MB
  • 超 100MB 触发 Nginx 413 错误
tsx 复制代码
const formData = new FormData();
formData.append('file', file); // 500MB 一次性发送
await fetch('/api/upload', { method: 'POST', body: formData });

优化后

  • 5MB 分片 + 3 路并发 + 断点续传
  • 上传成功率 99.5%
  • 断网恢复后只续传剩余分片
  • 上传耗时从 180s 降至 72s
tsx 复制代码
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// 查询已上传分片 → 跳过 → 并发上传剩余 → 合并

对比图

xychart-beta title "500MB 文件上传成功率 (%)" x-axis ["优化前 (直传)", "优化后 (分片)"] y-axis "成功率 (%)" 0 --> 105 bar [62, 99]
xychart-beta title "500MB 文件上传耗时 (s)" x-axis ["优化前", "优化后"] y-axis "耗时 (s)" 0 --> 200 bar [180, 72]
graph LR subgraph 分片上传断点续传示意 C1[1 ✅] --> C2[2 ✅] --> C3[3 ✅] --> C4[4 ✅] --> C5[5 ✅] --> C6[6 ⚠️断网] C6 --> C7[7 🔄] --> C8[8 🔄] --> C9[9 🔄] --> C10[10 ⬜] --> C11[11 ⬜] --> C12[12 ⬜] end style C1 fill:#27ae60,color:#fff style C2 fill:#27ae60,color:#fff style C3 fill:#27ae60,color:#fff style C4 fill:#27ae60,color:#fff style C5 fill:#27ae60,color:#fff style C6 fill:#e67e22,color:#fff style C7 fill:#3498db,color:#fff style C8 fill:#3498db,color:#fff style C9 fill:#3498db,color:#fff style C10 fill:#ddd,color:#999 style C11 fill:#ddd,color:#999 style C12 fill:#ddd,color:#999
指标 优化前 优化后 变化
上传成功率 62% 99.5% 提升了 60%
上传耗时 180s 72s 减少了 60%
断网重传量 100% ~5% 减少了 95%

14. 客户端图片压缩

分类:文件上传 / Canvas API

优化前

  • 10 张手机照片原图直传(4032×3024)
  • 均值 5MB/张,总计 50MB
  • 4G 网络上传耗时 45s
tsx 复制代码
Array.from(files).forEach(file => {
  formData.append('images', file); // 每张 4-6MB 原图
});

优化后

  • Canvas 压缩 + 缩放到 1920px + quality 0.8
  • 均值 300KB/张,总计 3MB
  • 上传耗时 3s
tsx 复制代码
function compressImage(file: File, maxWidth = 1920, quality = 0.8): Promise<Blob> {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      const ratio = Math.min(1, maxWidth / img.width);
      canvas.width = img.width * ratio;
      canvas.height = img.height * ratio;
      canvas.getContext('2d')!.drawImage(img, 0, 0, canvas.width, canvas.height);
      canvas.toBlob(blob => resolve(blob!), 'image/jpeg', quality);
    };
    img.src = URL.createObjectURL(file);
  });
}

对比图

xychart-beta title "10 张图片上传体积 (MB)" x-axis ["优化前 (原图)", "优化后 (压缩)"] y-axis "体积 (MB)" 0 --> 55 bar [50, 3]
指标 优化前 优化后 变化
上传体积 50MB 3MB 减少了 94%
上传时间 45s 3s 减少了 93%
存储成本 50MB/批 3MB/批 减少了 94%

15. DNS 预解析 + 预连接

分类:网络优化 / 首屏体验

优化前

  • 首次请求需完整建立连接
  • DNS(80ms) + TCP(80ms) + TLS(120ms) = 280ms
  • 5 个域名累计延迟 1.4s
html 复制代码
<!-- 无任何预处理,连接在使用时才建立 -->
<link rel="stylesheet" href="https://cdn.example.com/styles/main.css" />

优化后

  • dns-prefetch + preconnect 提前建立连接
  • 首次请求连接延迟 ~0ms
html 复制代码
<link rel="dns-prefetch" href="//cdn.example.com" />
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<link rel="preload" href="https://cdn.example.com/fonts/main.woff2" as="font" type="font/woff2" crossorigin />

对比图

gantt title 单域名首次连接耗时分解 dateFormat X axisFormat %s section 优化前 DNS 解析 80ms :crit, 0, 8 TCP 握手 80ms :crit, 8, 16 TLS 协商 120ms :crit, 16, 28 section 优化后 预建立完成 0ms :active, 0, 1
指标 优化前 优化后 变化
单域名连接延迟 280ms ~0ms 减少了 100%
5域名累计延迟 1.4s ~0ms 减少了 100%

16. 前端缓存分层策略

分类:HTTP 缓存

优化前

  • Cache-Control: no-cache,无缓存策略
  • 每次访问重新下载 3.2MB
  • 用户日均 8 次访问消耗 25.6MB/天

优化后

  • 带 hash 静态资源强缓存 1 年 + HTML 协商缓存 + Service Worker
  • 二次访问仅加载 ~5KB(HTML)
  • CDN 带宽消耗 减少 85%
nginx 复制代码
# 静态资源强缓存
location ~* \.(js|css|png|woff2)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}
# HTML 协商缓存
location ~* \.html$ {
    add_header Cache-Control "no-cache";
    etag on;
}

对比图

xychart-beta title "二次访问资源加载耗时 (ms)" x-axis ["优化前 (无缓存)", "优化后 (缓存命中)"] y-axis "耗时 (ms)" 0 --> 2000 bar [1800, 120]
graph TB subgraph 缓存策略层次 SW[Service Worker
离线可用] --> Strong[强缓存 immutable
JS / CSS / 图片 / 字体] Strong --> Negotiate[协商缓存 ETag
HTML 入口文件] end style SW fill:#3498db,color:#fff style Strong fill:#27ae60,color:#fff style Negotiate fill:#f39c12,color:#fff
指标 优化前 优化后 变化
二次加载时间 1.8s 120ms 减少了 93%
CDN 带宽 25.6MB/天/人 3.8MB/天/人 减少了 85%

17. 字体子集化 + font-display

分类:字体优化 / 内容可见性

优化前

  • 中文全量字体包 8.5MB
  • FOIT:文字空白期 4.2s,用户以为页面崩溃
css 复制代码
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-font-full.ttf') format('truetype');
  /* 未指定 font-display,默认阻塞渲染 */
}

优化后

  • font-spider 子集化 + WOFF2 = 180KB
  • font-display: swap,文字空白 0s
css 复制代码
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-font-subset.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+4E00-9FFF, U+0020-007E;
}

对比图

xychart-beta title "字体文件体积 (KB)" x-axis ["优化前 (全量TTF)", "优化后 (子集WOFF2)"] y-axis "体积 (KB)" 0 --> 9000 bar [8500, 180]
gantt title 文字可见性时间线 dateFormat X axisFormat %s section 优化前 FOIT 文字不可见 :crit, 0, 42 字体加载完才可见 :active, 42, 50 section 优化后 FOUT 系统字体立即可见 → 自定义字体无缝切换 :active, 0, 50
指标 优化前 优化后 变化
字体体积 8.5MB 180KB 减少了 98%
文字空白期 4.2s 0s 减少了 100%

18. 骨架屏消除白屏感知

分类:用户体验

优化前

  • 数据加载期间返回 null --- 完全白屏
  • FCP 2.5s
  • 35% 用户在白屏期重复刷新
tsx 复制代码
if (isLoading) return null; // 白屏

优化后

  • 骨架屏即时渲染,模拟真实页面布局
  • FCP 300ms
  • 重复刷新率降至 4%
tsx 复制代码
if (isLoading) return <DashboardSkeleton />;
css 复制代码
.skeleton-line {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

对比图

graph LR subgraph 优化前 A[页面加载] --> B[白屏 2.5s] B --> C[内容展示] end subgraph 优化后 D[页面加载] --> E[骨架屏 300ms] E --> F[内容展示] end style B fill:#e74c3c,color:#fff style E fill:#27ae60,color:#fff style C fill:#3498db,color:#fff style F fill:#3498db,color:#fff
xychart-beta title "FCP 时间对比 (ms)" x-axis ["优化前 (白屏)", "优化后 (骨架屏)"] y-axis "FCP (ms)" 0 --> 2800 bar [2500, 300]
指标 优化前 优化后 变化
FCP 2.5s 300ms 减少了 88%
重复刷新率 35% 4% 减少了 89%
页面留存率 --- --- 提升了 25%

19. 流式 Excel 导出

分类:文件处理

优化前

  • 10万行 × 30列 在内存中一次性构建 WorkBook
  • 内存峰值 1.2GB,频繁触发 Tab 崩溃
  • 主线程阻塞 12s ,导出成功率 40%
tsx 复制代码
const ws = XLSX.utils.json_to_sheet(data); // 10 万行一次性转换
XLSX.writeFile(wb, 'report.xlsx');          // 12 秒阻塞

优化后

  • exceljs 流式写入 + Web Worker
  • 内存峰值 85MB ,主线程 0ms 阻塞
  • 导出成功率 99.8%
tsx 复制代码
// Web Worker 中逐行写入
const sheet = workbook.addWorksheet('Report');
for (let i = 0; i < data.length; i++) {
  sheet.addRow(data[i]).commit(); // 逐行写入并释放内存
}

对比图

xychart-beta title "内存峰值对比 (MB)" x-axis ["优化前 (全量构建)", "优化后 (流式写入)"] y-axis "内存 (MB)" 0 --> 1300 bar [1200, 85]
xychart-beta title "导出成功率 (%)" x-axis ["优化前", "优化后"] y-axis "成功率 (%)" 0 --> 105 bar [40, 99]
指标 优化前 优化后 变化
内存峰值 1.2GB 85MB 减少了 93%
主线程阻塞 12s 0ms 减少了 100%
导出成功率 40% 99.8% 提升了 150%

20. 首屏关键 CSS 内联

分类:CSS / 首屏渲染

优化前

  • 320KB 全量 CSS 阻塞渲染
  • CSS 下载耗时 800ms
  • FCP 1.6s
html 复制代码
<link rel="stylesheet" href="/styles/main.css" /> <!-- 320KB 阻塞 -->

优化后

  • 12KB 关键 CSS 内联到 <style>,非关键 CSS 异步加载
  • FCP 400ms
html 复制代码
<style>/* 首屏关键样式 12KB */</style>
<link rel="preload" href="/styles/main.css" as="style" onload="this.rel='stylesheet'" />

对比图

gantt title 首屏渲染时间线 dateFormat X axisFormat %s section 优化前 HTML 解析 :active, 0, 3 CSS 下载 320KB 阻塞 :crit, 3, 11 FCP @ 1.6s :milestone, 11, 11 section 优化后 HTML + 内联 CSS 解析 :active, 0, 4 FCP @ 400ms :milestone, 4, 4 CSS 异步加载 :done, 4, 11
指标 优化前 优化后 变化
FCP 1.6s 400ms 减少了 75%

21. IntersectionObserver 按需渲染

分类:JavaScript / 滚动性能

优化前

  • scroll 事件 + getBoundingClientRect 判断可见性
  • 每秒 30-60 次回调,强制同步布局
  • CPU 45% ,帧率 35fps
  • 15 个图表一次性初始化 2.8s
tsx 复制代码
window.addEventListener('scroll', () => {
  elements.forEach(el => {
    const rect = el.getBoundingClientRect(); // 每次触发 Layout
  });
});

优化后

  • IntersectionObserver 浏览器原生异步通知
  • CPU 3% ,帧率 60fps
  • 首屏仅初始化 2 个图表 0.4s
tsx 复制代码
const observer = new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {
    setIsVisible(true);
    observer.unobserve(entry.target);
  }
}, { rootMargin: '200px' });
observer.observe(el);

对比图

xychart-beta title "滚动时 CPU 占用率 (%)" x-axis ["优化前 (scroll)", "优化后 (IO)"] y-axis "CPU (%)" 0 --> 50 bar [45, 3]
xychart-beta title "首屏图表初始化耗时 (ms)" x-axis ["优化前 (15个全部)", "优化后 (2个按需)"] y-axis "耗时 (ms)" 0 --> 3000 bar [2800, 400]
指标 优化前 优化后 变化
CPU 占用 45% 3% 减少了 93%
首屏初始化 2.8s 0.4s 减少了 86%
滚动帧率 35fps 60fps 提升了 71%

22. WebSocket 替代轮询

分类:网络通信 / 实时性

优化前

  • 3 秒间隔轮询,每次返回全量 45KB
  • 200 人在线 → 服务端 4000 QPS/min
  • 95% 响应数据无变化 --- 纯浪费
  • 数据延迟最大 3 秒
tsx 复制代码
const timer = setInterval(async () => {
  const res = await fetch('/api/tickets');
  setTickets(await res.json());
}, 3000);

优化后

  • WebSocket 长连接,服务端仅在数据变更时推送增量
  • 服务端 50 推送/min
  • 传输数据量 减少 97%
  • 数据延迟 毫秒级
tsx 复制代码
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'update') {
    setTickets(prev => {
      const next = new Map(prev);
      next.set(msg.data.id, { ...next.get(msg.data.id)!, ...msg.data });
      return next;
    });
  }
};

对比图

xychart-beta title "服务端 QPS/min (200人在线)" x-axis ["优化前 (轮询)", "优化后 (WebSocket)"] y-axis "QPS / min" 0 --> 4500 bar [4000, 50]
gantt title 数据更新时间线 (15秒窗口, 第7秒发生变更) dateFormat X axisFormat %s section 优化前 - 轮询 无变更请求 :crit, 0, 3 无变更请求 :crit, 3, 6 无变更请求 :crit, 6, 9 命中变更 :active, 9, 12 无变更请求 :crit, 12, 15 section 优化后 - WebSocket 持久连接保持中 :done, 0, 15 变更即时推送 @ 7s :active, 7, 8
指标 优化前 优化后 变化
服务端 QPS 4,000/min 50/min 减少了 99%
传输数据量 900KB/min 27KB/min 减少了 97%
数据延迟 最大 3s 毫秒级 减少了 99%

全部 22 条优化总览

# 优化手段 分类 核心指标 优化前 优化后 提升幅度
1 React.memo React 渲染耗时 120ms 8ms 减少了 93%
2 虚拟滚动 DOM DOM 节点 15,000+ 60 减少了 99.6%
3 代码分割 构建 首屏 JS 860KB 210KB 减少了 76%
4 防抖+缓存 网络 请求数 11 次 1-2 次 减少了 90%
5 WebP+懒加载 资源 图片体积 7MB 1.7MB 减少了 76%
6 useMemo React 计算耗时 65ms 0.1ms 减少了 99%
7 Tree Shaking 构建 依赖体积 530KB 28KB 减少了 95%
8 状态下沉 React 渲染组件数 47 3 减少了 94%
9 transform 动画 CSS 帧率 24fps 60fps 提升了 150%
10 事件委托 JS 监听器数 1,500 1 减少了 99.9%
11 Web Worker JS 主线程阻塞 3.8s 0ms 减少了 100%
12 请求合并 网络 请求数 8 1 减少了 87.5%
13 分片上传 上传 成功率 62% 99.5% 提升了 60%
14 客户端压缩 上传 上传体积 50MB 3MB 减少了 94%
15 DNS 预连接 网络 连接延迟 280ms ~0ms 减少了 100%
16 缓存分层 缓存 二次加载 1.8s 120ms 减少了 93%
17 字体子集化 字体 字体体积 8.5MB 180KB 减少了 98%
18 骨架屏 UX FCP 2.5s 300ms 减少了 88%
19 流式导出 文件 内存峰值 1.2GB 85MB 减少了 93%
20 关键CSS内联 CSS FCP 1.6s 400ms 减少了 75%
21 IntersectionObserver JS CPU 占用 45% 3% 减少了 93%
22 WebSocket 网络 QPS/min 4,000 50 减少了 99%
相关推荐
yuki_uix2 小时前
HTTP 缓存策略:新鲜度与速度的权衡艺术
前端·面试
哈撒Ki2 小时前
快速入门 Dart 语言
前端·flutter·dart
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day5(简介接口对接+规划AI自动化卫星数据生成工作流)
前端·人工智能·3d·ai·自动化
毛骗导演2 小时前
Claude Code Agent 实现原理深度剖析
前端·架构
星晨雪海2 小时前
若依框架原有页面功能进行了点位管理模块完整改造(3)
开发语言·前端·javascript
morethanilove2 小时前
新建vue3 + ts +vite 项目
前端·javascript·vue.js
GISer_Jing2 小时前
微软AI战略全景:从基础设施到智能体生态
前端·人工智能·microsoft
发际线向北2 小时前
0x03 单元测试与Junit
前端·单元测试
忆往wu前2 小时前
搞懂 SPA 再学路由!Vue Router 从0到完善 + 嵌套路由一次性梳理
前端·vue.js