前端性能优化策略
打造快速响应的现代Web应用
性能指标
理解核心性能指标是优化的第一步:
核心Web指标: LCP(最大内容绘制)、FID(首次输入延迟)、CLS(累积布局偏移)。
1. 加载性能优化
1.1 资源优化
<!-- 图片优化 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.avif" type="image/avif">
<img src="image.jpg" alt="示例图片" loading="lazy" decoding="async">
</picture>
<!-- 字体优化 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 脚本优化 -->
<script src="app.js" defer></script>
<!-- 或 -->
<script src="app.js" async></script>
<!-- 样式优化 -->
<link rel="preload" href="style.css" as="style">
<link rel="stylesheet" href="style.css">
1.2 代码分割
// Webpack动态导入
const module = await import('./module.js');
// React懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// Vue懒加载
const LazyComponent = () => import('./LazyComponent.vue');
// 路由懒加载(React Router v6)
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Home />
},
{
path: 'about',
lazy: () => import('./pages/About')
},
{
path: 'dashboard',
lazy: () => import('./pages/Dashboard')
}
]
}
]);
2. 渲染性能优化
2.1 减少重排重绘
// 避免频繁操作DOM
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
document.getElementById('container').appendChild(fragment);
// 使用虚拟DOM
const items = Array.from({length: 1000}, (_, i) => `Item ${i}`);
const list = items.map(item =>
<div key={item}>{item}</div>
);
// 避免强制同步布局
// 错误示例
function resizeAllParagraphsToMatchBlockWidth() {
const paragraphs = document.querySelectorAll('p');
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = `${paragraphs[i].offsetWidth}px`;
}
}
// 正确示例
function resizeAllParagraphsToMatchBlockWidth() {
const paragraphs = document.querySelectorAll('p');
const width = paragraphs[0].offsetWidth; // 只读取一次
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = `${width}px`;
}
}
2.2 使用Web Workers
// main.js
const worker = new Worker('worker.js');
// 发送数据到Worker
worker.postMessage({
type: 'calculate',
data: largeDataset
});
// 接收Worker结果
worker.onmessage = function(event) {
const result = event.data;
console.log('计算结果:', result);
};
// worker.js
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'calculate') {
// 执行耗时计算
const result = performHeavyCalculation(data);
// 返回结果
self.postMessage(result);
}
};
function performHeavyCalculation(data) {
// 复杂的计算逻辑
return data.map(item => item * 2);
}
3. 缓存策略
// Service Worker缓存策略
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response; // 缓存命中
}
// 克隆请求(请求体只能使用一次)
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then(response => {
// 检查是否是有效响应
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应(响应体只能使用一次)
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
4. 网络优化
// HTTP/2 Server Push配置
# nginx.conf
http2_push /style.css;
http2_push /app.js;
// 使用preconnect和prefetch
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
// 资源提示
<link rel="prefetch" href="/next-page.html">
<link rel="prerender" href="/next-page.html">
// 压缩配置
# nginx gzip配置
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
image/svg+xml;
// Brotli压缩(更高效)
brotli on;
brotli_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
brotli_static on;
5. 监控与分析工具
5.1 性能监控
// 使用Performance API
const measurePerformance = () => {
const navigationEntries = performance.getEntriesByType('navigation');
const paintEntries = performance.getEntriesByType('paint');
if (navigationEntries.length > 0) {
const navEntry = navigationEntries[0];
console.log('页面加载时间:', navEntry.loadEventEnd - navEntry.startTime);
console.log('DNS查询时间:', navEntry.domainLookupEnd - navEntry.domainLookupStart);
console.log('TCP连接时间:', navEntry.connectEnd - navEntry.connectStart);
console.log('首字节时间:', navEntry.responseStart - navEntry.requestStart);
}
if (paintEntries.length > 0) {
const firstPaint = paintEntries.find(entry => entry.name === 'first-paint');
const firstContentfulPaint = paintEntries.find(entry => entry.name === 'first-contentful-paint');
if (firstPaint) console.log('首次绘制:', firstPaint.startTime);
if (firstContentfulPaint) console.log('首次内容绘制:', firstContentfulPaint.startTime);
}
// 监听长任务
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log('长任务:', entry);
}
});
observer.observe({ entryTypes: ['longtask'] });
};
// 监听核心Web指标
const observeCLS = () => {
let clsValue = 0;
let clsEntries = [];
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
clsEntries.push(entry);
console.log('CLS增加:', entry.value, '总CLS:', clsValue);
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
};
// 监听LCP
const observeLCP = () => {
const observer = new PerformanceObserver(list => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime, lastEntry);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
};
6. 框架特定优化
6.1 React优化
// 使用React.memo避免不必要的渲染
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
});
// 使用useMemo和useCallback
const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// 虚拟列表优化
import { FixedSizeList as List } from 'react-window';
function MyList({ items }) {
return (
<List
height={400}
itemCount={items.length}
itemSize={50}
width={300}
>
{({ index, style }) => (
<div style={style}>
Item {items[index]}
</div>
)}
</List>
);
}
// 代码分割
const OtherComponent = React.lazy(() => import('./OtherComponent'));
7. 构建工具优化
// webpack.config.js优化示例
module.exports = {
mode: 'production',
// 代码分割
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {
minChunks: 2,
name: 'common',
chunks: 'async',
minSize: 0
}
}
},
runtimeChunk: 'single',
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true
}
}
})
]
},
// 模块配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
},
// 插件配置
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
],
// 性能提示
performance: {
hints: 'warning',
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};
总结
前端性能优化是一个持续的过程,需要从多个维度综合考虑。关键是测量、分析、优化、再测量,形成一个良性循环。
关键建议:
- 始终以用户为中心,关注核心Web指标
- 实施渐进式优化策略
- 建立持续的性能监控机制
- 团队协作,性能优化人人有责