前端性能优化
一、性能指标
1.1 Core Web Vitals
| 指标 | 全称 | 说明 | 良好值 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容绘制 | < 2.5s |
| FID | First Input Delay | 首次输入延迟 | < 100ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | < 0.1 |
| INP | Interaction to Next Paint | 交互到下一帧绘制 | < 200ms |
1.2 其他重要指标
| 指标 | 说明 |
|---|---|
| FCP | 首次内容绘制 |
| TTI | 可交互时间 |
| TBT | 总阻塞时间 |
| TTFB | 首字节时间 |
1.3 性能监测
javascript
// Performance API
const [entry] = performance.getEntriesByType('navigation');
console.log('DNS 解析:', entry.domainLookupEnd - entry.domainLookupStart);
console.log('TCP 连接:', entry.connectEnd - entry.connectStart);
console.log('请求响应:', entry.responseEnd - entry.requestStart);
console.log('DOM 解析:', entry.domContentLoadedEventEnd - entry.responseEnd);
// Web Vitals 库
import { getLCP, getFID, getCLS } from 'web-vitals';
getLCP(console.log);
getFID(console.log);
getCLS(console.log);
// PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
}
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });二、加载性能优化
2.1 资源压缩
javascript
// Webpack 配置
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin(), // JS 压缩
new CssMinimizerPlugin() // CSS 压缩
]
}
};
// 启用 Gzip/Brotli
// Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_types text/plain text/css application/json application/javascript;2.2 代码分割
javascript
// 动态导入
const module = await import('./heavy-module.js');
// React 懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// Webpack SplitChunks
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}2.3 Tree Shaking
javascript
// package.json
{
"sideEffects": false // 标记无副作用
}
// 使用 ES Modules
import { debounce } from 'lodash-es'; // ✅
// import _ from 'lodash'; // ❌ 全量引入2.4 资源预加载
html
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/font.woff2" as="font" crossorigin>
<link rel="preload" href="/critical.css" as="style">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.js">
<!-- 模块预加载 -->
<link rel="modulepreload" href="/module.js">2.5 图片优化
html
<!-- 响应式图片 -->
<img
src="small.jpg"
srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
loading="lazy"
decoding="async"
alt="description"
>
<!-- 现代格式 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="description">
</picture>css
/* 骨架屏占位 */
.image-placeholder {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}三、运行时性能优化
3.1 长任务拆分
javascript
// ❌ 长任务阻塞主线程
function processLargeArray(array) {
array.forEach(item => heavyProcess(item));
}
// ✅ 使用 requestIdleCallback 拆分
function processWithIdle(array) {
let index = 0;
function process(deadline) {
while (index < array.length && deadline.timeRemaining() > 0) {
heavyProcess(array[index++]);
}
if (index < array.length) {
requestIdleCallback(process);
}
}
requestIdleCallback(process);
}
// ✅ 使用 scheduler.yield() (实验性)
async function processWithYield(array) {
for (const item of array) {
heavyProcess(item);
if (navigator.scheduling?.isInputPending()) {
await scheduler.yield();
}
}
}3.2 虚拟列表
javascript
// 只渲染可见区域
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
return (
<div style={{ height: containerHeight, overflow: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}>
<div style={{ height: items.length * itemHeight }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map(item => <Item key={item.id} data={item} />)}
</div>
</div>
</div>
);
}3.3 防抖与节流
javascript
// 防抖:延迟执行,重置计时
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:固定频率执行
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用
window.addEventListener('scroll', throttle(handleScroll, 100));
input.addEventListener('input', debounce(search, 300));3.4 Web Worker
javascript
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeData });
worker.onmessage = (e) => console.log(e.data);
// worker.js
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};四、渲染性能优化
4.1 避免强制同步布局
javascript
// ❌ 读写交错
elements.forEach(el => {
el.style.width = container.offsetWidth + 'px'; // 每次都触发重排
});
// ✅ 先读后写
const width = container.offsetWidth;
elements.forEach(el => {
el.style.width = width + 'px';
});4.2 使用 CSS contain
css
.card {
contain: layout paint; /* 隔离重排重绘范围 */
}
.offscreen {
content-visibility: auto; /* 跳过屏幕外内容渲染 */
}4.3 优化动画
css
/* 只改变 transform 和 opacity */
.animated {
transform: translateX(100px);
opacity: 0.5;
will-change: transform, opacity;
}五、网络优化
5.1 使用 HTTP/2
# 优势
- 多路复用
- 头部压缩
- 服务器推送5.2 CDN 加速
javascript
// 静态资源使用 CDN
const cdnUrl = 'https://cdn.example.com';
const imageSrc = `${cdnUrl}/images/hero.webp`;5.3 Service Worker 缓存
javascript
// 缓存优先策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});六、高频面试题
Q1: 如何优化 LCP?
- 优化服务器响应时间 (TTFB)
- 预加载关键资源
- 优化图片 (WebP/AVIF, 响应式, lazy loading)
- 移除阻塞渲染的资源
Q2: 如何减少 CLS?
- 为图片/视频设置明确尺寸
- 避免在现有内容上方插入内容
- 使用 transform 做动画
- 预留广告位空间
Q3: 如何优化首屏加载?
- 代码分割 + 懒加载
- 关键 CSS 内联
- 预加载关键资源
- SSR/SSG
- 骨架屏
Q4: 列表渲染卡顿如何优化?
- 虚拟列表
- 分页加载
- requestIdleCallback 拆分任务
- Web Worker 处理数据