性能优化这件事,说简单也简单,说复杂也复杂。核心思想一句话:让浏览器少干活,还得干得快。
今天这篇文章,我们从最基础的 重绘、重排 开始,聊到 资源加载、JS 执行、框架优化、缓存、网络、首屏渲染 ,再配上 性能指标、工具和面试连招,让你从理论到实战全覆盖。
一、重绘 & 重排:浏览器最怕的"体力活"
浏览器的渲染流程大概是这样的:
布局(Layout) → 绘制(Paint) → 合成(Composite)
两个关键概念:
- 重绘(Repaint) 样式改变但位置没动,比如颜色、背景。
- 重排(Reflow) 尺寸或位置发生变化,需要重新计算布局。
口诀:
重排一定会触发重绘,重绘不一定触发重排。重排开销更大!
1. 批量修改 DOM
❌ 不推荐:
js
const el = document.getElementById('myEl');
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';
✅ 推荐:
js
el.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// 或者直接通过类名
el.className = 'my-class';
2. 使用文档碎片
批量添加节点用 document.createDocumentFragment
,只触发一次渲染:
js
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const el = document.createElement('div');
fragment.appendChild(el);
}
document.body.appendChild(fragment);
3. 脱离文档流后再修改
js
const el = document.getElementById('myEl');
el.style.display = 'none';
// 批量修改
el.style.display = 'block';
4. 缓存布局信息
避免重复触发重排:
js
// ❌ 每次循环都会触发重排
for (let i = 0; i < 100; i++) {
el.style.top = el.offsetTop + 1 + 'px';
}
// ✅ 缓存 offsetTop
let top = el.offsetTop;
for (let i = 0; i < 100; i++) {
el.style.top = ++top + 'px';
}
5. 用 transform
代替 top/left
js
// ❌ 会触发布局
el.style.left = '100px';
// ✅ 只触发重绘
el.style.transform = 'translateX(100px)';
二、资源加载优化
少加载、不阻塞、格式更高效:
-
图片懒加载
html<img loading="lazy" src="xxx.jpg">
-
路由懒加载 / 代码分割
jsconst Home = React.lazy(() => import('./Home'));
-
资源预加载
html<link rel="dns-prefetch" href="//cdn.example.com"> <link rel="preload" href="/main.css"> <link rel="prefetch" href="/next-page.js">
-
WebP 格式图片:小一半体积,质量无损。
-
字体图标库:减少几十个 HTTP 请求。
三、JS 执行优化
- 防抖、节流:减少高频事件触发次数。
- Web Worker:让复杂计算不堵主线程。
- requestAnimationFrame:动画更丝滑。
- requestIdleCallback:浏览器闲时干活,React Fiber 就用它实现调度。
四、框架层优化
React/Vue 项目常见套路:
memo / useMemo / useCallback
避免不必要渲染。- 按需加载组件库,如
shadcn-ui
、antd
。 - 列表渲染合理使用
key
,减少 Diff 计算。
五、缓存策略
缓存是性能优化的"性价比王者"。
-
强缓存(不发请求)
httpCache-Control: max-age=3600 Expires: Wed, 21 Oct 2025 07:28:00 GMT
-
协商缓存(发请求,返回 304)
httpLast-Modified / If-Modified-Since ETag / If-None-Match
-
本地缓存
localStorage
sessionStorage
cookie
六、网络优化
-
CDN 加速:资源离用户更近。
-
Gzip 压缩:资源小,加载快。
-
HTTP/2 多路复用:一个连接跑多请求。
-
DNS 预解析:
html<link rel="dns-prefetch" href="//cdn.example.com">
七、首屏优化
- SSR(服务端渲染):先把 HTML 渲染好再发给浏览器。
- 骨架屏:用户看起来更快。
- HTTP/2 Server Push:首屏资源提前推送。
八、性能测试工具
- Chrome Performance 定位瓶颈,分析渲染和脚本耗时。
- Lighthouse 性能、无障碍、SEO 等多维打分。
常见优化建议:
- 首屏 JS、CSS 体积减小(代码分割)
- 图片用 WebP
- 动画尽量用
transform
- 按需加载组件库
九、核心性能指标
- FCP(First Contentful Paint) 首次内容绘制时间。
- LCP(Largest Contentful Paint) 最大内容绘制时间,影响用户"页面加载完毕"的感知。
十、面试高频问题
性能优化在面试里是高频考点,以下是一些常见题型和答题套路。
Q1:重绘和重排的区别?怎么优化?
-
区别
- 重绘:样式变化但位置没变。
- 重排:尺寸或位置改变,需要重新布局。
-
优化
- 批量修改 DOM
- 使用文档碎片
- 脱离文档流操作
- 缓存布局信息
- 用
transform
代替top/left
Q2:首屏加载优化有哪些方案?
- 代码分割、懒加载、按需加载
- Gzip、CDN 加速
- SSR、骨架屏
- 强缓存 + 协商缓存组合拳
- HTTP/2 多路复用
Q3:长列表如何优化?
- 合理使用
key
- 虚拟列表技术(
react-window
) - 配合
memo
、useCallback
避免重复渲染
Q4:如何监控和分析性能?
- 开发阶段:Chrome Performance、Lighthouse
- 线上阶段:埋点上报 FCP、LCP、CLS;Sentry/ARMS 做监控
Q5:代码优化可以做哪些?
- 防抖、节流减少高频调用
- Web Worker 分担计算压力
- 动画用
requestAnimationFrame
- 减少无意义的深拷贝或频繁 state 更新
Q6:首页很慢,怎么排查?
- 资源问题:JS/CSS 是否太大?图片是否优化?
- 网络问题:DNS 解析、CDN 节点延迟。
- 渲染问题:同步 JS 阻塞渲染。
- 缓存问题:是否未命中缓存。
加分技巧:
答题时用"场景 + 措施 + 效果"的模式:
"项目首页加载慢,排查发现 JS 包 3M,通过 Webpack 分包和懒加载降到 500KB,首屏渲染从 3 秒降到 1.5 秒。"
这样的回答更有说服力!
结语
性能优化不是某个大招,而是一系列"小动作"的积累:
- 写代码时减少无意义的 DOM 操作
- 打包部署时合理做缓存和分割
- 上线后持续监控指标,优化体验
做多了,你会发现,项目从"能跑"到"飞起",就差这些细节。