前端性能优化
性能优化概述
前端性能优化是提升用户体验的关键,主要包括:
- 加载性能:减少首屏加载时间
- 运行时性能:提升页面交互流畅度
- 资源优化:减少资源体积和请求数量
加载性能优化
1. 代码分割和懒加载
路由懒加载:
// Vue Router
const routes = [
{
path: '/home',
component: () => import('./views/Home.vue'),
},
];
// React Router
const Home = lazy(() => import('./views/Home'));
组件懒加载:
// Vue
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
);
// React
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));
动态导入:
// 按需加载
if (condition) {
import('./module').then(module => {
module.doSomething();
});
}
2. 资源压缩
代码压缩:
- 生产环境启用代码压缩(Terser、UglifyJS)
- 启用 Gzip/Brotli 压缩
- 移除 console、debugger
配置示例:
// webpack
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
}),
],
}
// vite
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
}
3. 图片优化
图片格式选择:
- WebP:现代浏览器,体积小
- AVIF:最新格式,压缩率更高
- PNG:需要透明背景
- JPEG:照片类图片
- SVG:图标、简单图形
图片懒加载:
<!-- 原生懒加载 -->
<img src="image.jpg" loading="lazy" alt="description" />
<!-- Intersection Observer -->
<img data-src="image.jpg" class="lazy" alt="description" />
// 懒加载实现
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
图片压缩:
- 使用工具压缩:TinyPNG、ImageOptim
- 使用 CDN 自动压缩
- 响应式图片:
srcset、sizes
<img
srcset="
image-320w.jpg 320w,
image-640w.jpg 640w,
image-1024w.jpg 1024w
"
sizes="(max-width: 320px) 280px, (max-width: 640px) 600px, 1024px"
src="image-1024w.jpg"
alt="description"
/>
4. 减少 HTTP 请求
合并文件:
- 合并 CSS 文件
- 合并 JS 文件(注意代码分割)
- 使用雪碧图(CSS Sprites)
内联关键资源:
<!-- 内联关键 CSS -->
<style>
/* 关键 CSS */
</style>
<!-- 内联关键 JS -->
<script>
// 关键 JS
</script>
5. 使用 CDN
CDN 优势:
- 就近访问,减少延迟
- 减轻服务器压力
- 支持 HTTP/2
配置示例:
// webpack
output: {
publicPath: 'https://cdn.example.com/',
}
// vite
build: {
assetsDir: 'assets',
// 配置 CDN
}
6. 预加载和预连接
资源提示:
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://api.example.com" />
<!-- 预连接 -->
<link rel="preconnect" href="https://api.example.com" />
<!-- 预加载 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- 预获取 -->
<link rel="prefetch" href="next-page.html" />
<!-- 预渲染 -->
<link rel="prerender" href="next-page.html" />
7. 缓存策略
浏览器缓存:
- 强缓存:Cache-Control、Expires
- 协商缓存:ETag、Last-Modified
Service Worker 缓存:
// 缓存策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
运行时性能优化
1. 减少重排和重绘
优化 DOM 操作:
// ❌ 不好:多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';
// ✅ 好:一次重排
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// ✅ 更好:使用 class
element.className = 'new-style';
批量 DOM 操作:
// ❌ 不好
items.forEach(item => {
container.appendChild(item);
});
// ✅ 好:使用 DocumentFragment
const fragment = document.createDocumentFragment();
items.forEach(item => {
fragment.appendChild(item);
});
container.appendChild(fragment);
使用 transform 和 opacity:
/* ✅ 触发合成层,不触发重排重绘 */
.element {
transform: translateX(100px);
opacity: 0.5;
}
/* ❌ 触发重排 */
.element {
left: 100px;
top: 100px;
}
2. 防抖和节流
防抖(Debounce):
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 使用
const handleSearch = debounce((query) => {
// 搜索逻辑
}, 300);
节流(Throttle):
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用
const handleScroll = throttle(() => {
// 滚动处理
}, 100);
3. 虚拟滚动
适用场景:
- 长列表渲染
- 大量数据展示
实现思路:
class VirtualScroll {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.startIndex = 0;
this.endIndex = this.startIndex + this.visibleCount;
}
render() {
const visibleItems = this.items.slice(this.startIndex, this.endIndex);
// 渲染可见项
}
handleScroll() {
const scrollTop = this.container.scrollTop;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
this.endIndex = this.startIndex + this.visibleCount;
this.render();
}
}
4. 使用 Web Workers
将耗时任务移到 Worker:
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeData });
worker.onmessage = (e) => {
const result = e.data;
// 处理结果
};
// worker.js
self.onmessage = (e) => {
const { data } = e.data;
// 处理数据
const result = processData(data);
self.postMessage(result);
};
5. 优化事件处理
事件委托:
// ❌ 不好:每个元素绑定事件
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ 好:事件委托
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
及时移除事件监听:
// 组件卸载时移除
useEffect(() => {
const handleResize = () => {
// 处理逻辑
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
资源优化
1. Tree Shaking
确保 Tree Shaking 生效:
// ✅ 使用 ES modules
import { debounce } from 'lodash-es';
// ❌ 避免导入整个库
import _ from 'lodash';
2. 按需加载
组件库按需加载:
// ✅ 按需导入
import { Button } from 'antd';
// ❌ 避免全量导入
import * as Antd from 'antd';
3. 使用 WebP 和现代格式
图片格式检测:
function supportsWebP() {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
}
// 根据支持情况选择格式
const imageFormat = supportsWebP() ? 'webp' : 'jpg';
首屏优化
1. 关键渲染路径优化
优化步骤:
- 减少关键资源数量
- 减少关键资源大小
- 缩短关键渲染路径长度
关键 CSS 内联:
<head>
<style>
/* 关键 CSS */
.header { ... }
.hero { ... }
</style>
</head>
2. 骨架屏
实现骨架屏:
<template>
<div v-if="loading" class="skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-content"></div>
</div>
<div v-else>
<!-- 实际内容 -->
</div>
</template>
3. 服务端渲染(SSR)
优势:
- 首屏内容直接渲染
- SEO 友好
- 减少客户端渲染压力
性能监控
1. 性能指标
Web Vitals:
- LCP (Largest Contentful Paint):最大内容绘制
- FID (First Input Delay):首次输入延迟
- CLS (Cumulative Layout Shift):累积布局偏移
测量方法:
// LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ entryTypes: ['first-input'] });
2. 性能分析工具
Chrome DevTools:
- Performance 面板
- Lighthouse
- Network 面板
API:
// Performance API
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
console.log('页面加载时间:', pageLoadTime);
最佳实践总结
代码层面
- 代码分割和懒加载
- Tree Shaking
- 减少重排重绘
- 使用防抖节流
资源层面
- 图片优化和懒加载
- 资源压缩
- CDN 加速
- 缓存策略
网络层面
- 减少 HTTP 请求
- 使用 HTTP/2
- 预加载关键资源
- 使用 Service Worker
渲染层面
- 关键 CSS 内联
- 骨架屏
- 虚拟滚动
- SSR/SSG