前端性能优化
What — 是什么
前端性能优化是从网络、渲染、资源、代码四个维度提升页面加载速度和交互流畅度的系统性工程。
核心概念:
- Core Web Vitals:LCP(最大内容绘制)、FID/INP(交互延迟)、CLS(布局偏移)
- 加载优化:资源压缩、CDN、缓存、代码分割、懒加载
- 渲染优化:减少重排重绘、虚拟列表、requestAnimationFrame
- 网络优化:HTTP/2、预加载、预连接、减少请求数
关键特性:
- LCP < 2.5s / INP < 200ms / CLS < 0.1 为良好
- 优化应先度量再优化(DevTools / Lighthouse / Web Vitals)
- 性能是持续关注,不是一次性的
Why — 为什么
适用场景:
- 所有面向用户的 Web 应用
- SEO 排名(Google 将 Core Web Vitals 纳入排名因素)
- 用户留存(每慢 1s 转化率下降 7%)
优化维度:
| 维度 | 目标 | 关键手段 |
|---|---|---|
| 网络传输 | 减少传输量/次数 | 压缩、CDN、HTTP/2、缓存 |
| 资源加载 | 减少关键路径资源 | 代码分割、懒加载、预加载 |
| 渲染 | 减少重排重绘 | transform、虚拟列表、will-change |
| 运行时 | 减少 JS 执行时间 | 防抖节流、Web Worker、按需计算 |
How — 怎么用
快速上手
资源加载优化:
<head>
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="//cdn.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.js">
</head>
代码示例
代码分割与懒加载:
// React 路由懒加载
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
图片优化:
<!-- 懒加载 -->
<img src="photo.jpg" loading="lazy" alt="Photo">
<!-- 响应式图片 + 现代格式 -->
<picture>
<source type="image/avif" srcset="photo.avif">
<source type="image/webp" srcset="photo.webp">
<img src="photo.jpg" alt="Photo" width="800" height="600"
loading="lazy" decoding="async">
</picture>
<!-- Next.js Image 组件(自动优化) -->
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
虚拟列表(大列表渲染):
import { useVirtualizer } from '@tanstack/react-virtual';
function BigList({ items }) {
const parentRef = useRef();
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(item => (
<div key={item.key}
style={{ position: 'absolute', top: item.start, height: item.size }}>
{items[item.index].name}
</div>
))}
</div>
</div>
);
}
防抖与节流:
// 防抖:连续触发只执行最后一次
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// 节流:固定间隔执行一次
function throttle(fn, interval) {
let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn(...args);
}
};
}
// 使用
input.addEventListener('input', debounce(search, 300));
window.addEventListener('scroll', throttle(updatePosition, 100), { passive: true });
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| LCP 过大 | 关键资源阻塞渲染 | 内联关键 CSS,预加载关键图片,减少阻塞 JS |
| CLS 偏移 | 图片/广告/动态内容无预留空间 | 设置 width/height,aspect-ratio |
| 首屏 JS 过大 | 未做代码分割 | 路由懒加载 + 动态 import |
| 滚动卡顿 | 大列表 DOM 节点过多 | 虚拟列表 |
最佳实践
- 先用 Lighthouse 评分,针对性优化
- 图片用 WebP/AVIF + 懒加载 + 尺寸预留
- 路由级代码分割是标配
- 防抖搜索、节流滚动、虚拟大列表
面试题
Q1: Core Web Vitals 包含哪些指标?各自衡量什么?
Core Web Vitals 包含三个指标:LCP(Largest Contentful Paint)衡量加载性能,目标 < 2.5s;INP(Interaction to Next Paint)衡量交互响应性,目标 < 200ms;CLS(Cumulative Layout Shift)衡量视觉稳定性,目标 < 0.1。其中 INP 已取代 FID 成为新标准。
Q2: 首屏优化有哪些常见方案?
核心思路是减少关键路径资源:路由级代码分割(动态 import)减少 JS 体积;关键 CSS 内联、非关键 CSS 异步加载;图片用 WebP/AVIF + 懒加载 + 预加载首屏图片;服务端渲染(SSR)或静态生成(SSG)加速首屏绘制;CDN + HTTP/2 减少网络延迟。
Q3: 虚拟列表的原理是什么?为什么能优化大列表渲染?
虚拟列表只渲染可视区域内的 DOM 节点,通过监听滚动事件计算当前视口应显示的列表项索引,动态替换渲染内容,并用绝对定位或
transform偏移模拟完整滚动位置。万级数据列表仅渲染几十个 DOM 节点,显著减少重排重绘开销。
Q4: 代码分割有哪些策略?
路由级分割(
React.lazy+ 动态 import 按路由拆分);第三方库分割(splitChunks将node_modules单独打包为 vendor chunk);组件级分割(条件渲染的弹窗/面板用动态 import);按功能模块分割(将不同业务模块拆为独立 chunk 按需加载)。
相关链接: