楼层: 首页/ 软件技术/ 前端进阶/ 第 6 章 性能优化:让页面快到飞起
速

第 6 章 性能优化:让页面快到飞起

Web Vitals, Loading, Runtime, Memory

Google 给了三个硬性指标叫 Core Web Vitals,直接影响 SEO 和转化率:LCP(最大内容绘制 < 2.5s)、INP(交互到下次绘制 < 200ms)、CLS(累积布局偏移 < 0.1)。1 秒延迟能少 7% 转化。

加载性能:让包变小、加载变快

  • 代码分割:路由级懒加载 React.lazy + Suspense,首屏只下当前页面的 JS。
  • Tree Shaking:Rollup/Vite 自动删掉没用到的代码,前提是用 ESM 别用 CommonJS。
  • 压缩:服务器开 gzip / brotli 传输。
  • 图片优化:用 WebP / AVIF 格式;srcset 响应式图片;loading="lazy" 懒加载;写 width/height 防布局跳动。
  • 字体优化:font-display: swap 先用系统字再换 Web 字体;preload 预加载关键字体;子集化只留用到的字。
  • 第三方脚本:广告、统计代码加 async/defer,或用 Partytown 放到 Web Worker。
  • 缓存:HTTP 强缓存 + 协商缓存,CDN 分发。

路由懒加载

import { lazy, Suspense } from "react"; const Dashboard = lazy(() => import("./pages/Dashboard")); function App() { return ( <Suspense fallback={<p>加载中...</p>}> <Dashboard /> </Suspense> ); }

运行时性能:别让主线程堵死

  • 避免无谓重渲染:React.memo / useMemo / useCallback,但别过早优化,先看 DevTools Profiler。
  • 长列表:几千条数据用虚拟列表(react-window),只渲染可视区。
  • 防抖 / 节流:搜索输入防抖(停 300ms 才发请求)、滚动事件节流。
  • Web Worker:CPU 密集计算放后台线程,不阻塞 UI。new Worker() + postMessage。
  • 动画用 transform/opacity:不触发重排,只触发合成。

内存:别让浏览器越用越卡

前端三大内存泄漏

① 定时器没清:setInterval 在 useEffect 里没返回 clear。

② 事件监听没移除:addEventListener 配对 removeEventListener。

③ 闭包持有大对象:不用的引用要置 null。

用 Chrome DevTools → Memory 面板拍堆快照,对比两次快照能看到谁没被回收。

分析工具:先测量再优化

Lighthouse 一键跑分给建议;Performance 面板 录一次操作看哪里卡;Performance Insights 自动找长任务。别靠感觉优化,靠数据。

Core Web Vitals 三指标深挖

指标含义合格线怎么优化
LCP最大内容绘制(首屏主元素出来)< 2.5s优化字体/图片懒加载/预加载关键资源/CDN/减少阻塞的 JS CSS/SSR。
INP交互到下一次绘制(替代 FID)< 200ms减少主线程长任务/代码分割/防抖节流/避免布局抖动/虚拟列表/Worker。
CLS累积布局偏移(页面乱跳)< 0.1图片 video 写 width/height/font-display:swap/广告位预留空间/用 transform 动画。

加载性能:关键路径与资源策略

<!-- JS 加载策略 --> <script src="a.js" defer></script> <!-- defer:下载不阻塞,DOM 解析完按序执行 --> <script src="b.js" async></script> <!-- async:下完就执行,无序 --> <link rel="preload" href="font.woff2"> <!-- 预加载关键资源 --> <link rel="prefetch" href="/next-page"> <!-- 预判下个页面,闲时下 --> <link rel="preconnect" href="https://api.example.com"> <!-- 提前建连 --> /* 非关键 CSS 异步加载 */ <link rel="stylesheet" href="print.css" media="print" onload="this.media='all'">
  • 代码分割:路由级 React.lazy+Suspense,Vite manualChunks 把 react/antd 等不变库单独分包(更新后不全量失效)。
  • 图片:WebP/AVIF + srcset 响应式 + loading="lazy" 懒加载 + 写死宽高防 CLS。
  • 字体:font-display:swap + 预加载 + 子集化(fonttools)。
  • 传输:HTTP/2 多路复用、Gzip/Brotli 压缩、CDN 分发。

运行时:主线程别堵死

渲重排 vs 重绘 vs GPU 合成

重排(reflow):改 width/height/top/left/margin 等几何属性,浏览器要重新算布局,最贵。重绘(repaint):改 color/background/visibility,只画不改布局。GPU 合成:只改 transform/opacity,走合成线程,不碰主线程,最丝滑。动画永远用 transform/opacity,别用 left/width。

  • 防抖/节流:搜索输入防抖(停 300ms 才请求)、滚动 resize 节流。
  • 长列表:react-window / @tanstack/react-virtual 只渲染可视区。
  • Web Worker:CPU 密集计算丢后台线程,postMessage 通信,不阻塞 UI。
  • 内存泄漏三大件:定时器没 clear、事件监听没 remove、闭包持有大对象。用 DevTools Memory 拍堆快照对比。

性能监控

web-vitals 库把 LCP/INP/CLS 上报后端做真实用户监控(RUM);Lighthouse 实验室跑分给建议;Sentry 接性能监控 + 错误 + Source Map 还原堆栈。CI 里跑 Lighthouse CI 设性能预算(JS < 200KB、LCP < 2.5s),超了直接拦发布。

本章面试题

Q1. LCP/INP/CLS 各衡量什么?合格线?

看答案

LCP 首屏主内容绘制 <2.5s;INP 交互响应 <200ms;CLS 布局偏移 <0.1。

Q2. defer 和 async 的区别?

看答案

两者都不阻塞 HTML 解析;defer 下载完等 DOM 解析完按序执行;async 下完立刻执行,无序,互相无依赖的脚本用 async。

Q3. 为什么动画要用 transform 而不是 left?

看答案

改 left 触发重排重绘、走主线程;transform 走 GPU 合成,不触布局,帧率高不卡。

Q4. 虚拟列表解决什么问题?

看答案

长列表几千条 DOM 节点渲染/重排开销大,虚拟列表只渲染可视区的几十条,滚动时动态替换。