引言
前端性能直接影响用户体验,是衡量Web应用质量的重要指标。随着应用复杂度的增加和用户对性能要求的提升,前端性能优化变得越来越重要。本文将详细介绍前端性能优化的核心策略和最佳实践。
1. 资源加载优化
1.1 资源压缩与合并
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true
}
}
}
}
};
1.2 图片优化
// 图片懒加载
const lazyImages = 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);
}
});
});
lazyImages.forEach(img => imageObserver.observe(img));
// WebP格式支持
function loadWebPImage() {
const webpSupported = document.createElement('canvas')
.toDataURL('image/webp')
.indexOf('data:image/webp') === 0;
if (webpSupported) {
const images = document.querySelectorAll('img[data-webp]');
images.forEach(img => {
img.src = img.dataset.webp;
});
}
}
1.3 字体优化
/* 字体预加载 */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/CustomFont.woff2') format('woff2');
font-display: swap;
}
/* 关键字体内联 */
<style>
@font-face {
font-family: 'CriticalFont';
src: url('data:font/woff2;base64,d09GMgABAAAAAA...') format('woff2');
font-display: swap;
}
</style>
2. 代码分割与懒加载
2.1 React代码分割
import React, { Suspense, lazy } from 'react';
// 懒加载组件
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
// 路由级别的代码分割
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Suspense>
</Router>
);
}
2.2 Vue代码分割
// Vue路由懒加载
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
];
// 组件懒加载
const AsyncComponent = () => ({
component: import('./AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
});
3. 缓存策略
3.1 浏览器缓存
// Service Worker缓存策略
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
'/',
'/static/js/main.js',
'/static/css/main.css',
'/static/images/logo.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存优先策略
if (response) {
return response;
}
return fetch(event.request);
})
);
});
3.2 HTTP缓存头
# Nginx缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(html)$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
location /api/ {
expires 5m;
add_header Cache-Control "public, must-revalidate";
}
4. 渲染优化
4.1 React渲染优化
import React, { memo, useMemo, useCallback } from 'react';
// 使用memo避免不必要的重渲染
const ExpensiveComponent = memo(({ data, onUpdate }) => {
// 使用useMemo缓存计算结果
const processedData = useMemo(() => {
return data.map(item => ({
...item,
processed: item.value * 2
}));
}, [data]);
// 使用useCallback缓存函数
const handleClick = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
return (
<div>
{processedData.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}: {item.processed}
</div>
))}
</div>
);
});
// 虚拟滚动优化长列表
import { FixedSizeList as List } from 'react-window';
const VirtualList = ({ items }) => {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={35}
>
{Row}
</List>
);
};
4.2 Vue渲染优化
<template>
<div>
<div v-for="item in processedItems" :key="item.id">
:
</div>
</div>
</template>
<script>
export default {
name: 'OptimizedComponent',
props: {
items: {
type: Array,
required: true
}
},
computed: {
// 计算属性缓存
processedItems() {
return this.items.map(item => ({
...item,
processed: item.value * 2
}));
}
},
methods: {
// 防抖优化
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
handleInput: debounce(function(value) {
this.searchItems(value);
}, 300)
}
};
</script>
5. 网络优化
5.1 HTTP/2优化
// 服务器推送
app.get('/', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Link': '</static/css/main.css>; rel=preload; as=style, </static/js/main.js>; rel=preload; as=script'
});
res.end(html);
});
// 资源提示
const html = `
<!DOCTYPE html>
<html>
<head>
<link rel="preload" href="/static/css/main.css" as="style">
<link rel="preload" href="/static/js/main.js" as="script">
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="preconnect" href="//api.example.com">
</head>
<body>
<!-- 内容 -->
</body>
</html>
`;
5.2 CDN优化
// 动态CDN选择
function getOptimalCDN() {
const userLocation = getUserLocation();
const cdnMap = {
'CN': 'https://cdn-cn.example.com',
'US': 'https://cdn-us.example.com',
'EU': 'https://cdn-eu.example.com'
};
return cdnMap[userLocation] || cdnMap['US'];
}
// 资源加载优化
const cdnUrl = getOptimalCDN();
const script = document.createElement('script');
script.src = `${cdnUrl}/static/js/main.js`;
script.async = true;
document.head.appendChild(script);
6. 监控与分析
6.1 性能监控
// 核心Web指标监控
function monitorWebVitals() {
// LCP (Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID (First Input Delay)
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ entryTypes: ['first-input'] });
// CLS (Cumulative Layout Shift)
new PerformanceObserver((list) => {
let cls = 0;
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value;
}
});
console.log('CLS:', cls);
}).observe({ entryTypes: ['layout-shift'] });
}
// 自定义性能监控
function measurePerformance(name, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${name} took ${end - start}ms`);
// 发送到分析服务
sendToAnalytics({
metric: name,
duration: end - start,
timestamp: Date.now()
});
return result;
}
6.2 错误监控
// 全局错误监控
window.addEventListener('error', (event) => {
const error = {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
};
sendToErrorTracking(error);
});
// Promise错误监控
window.addEventListener('unhandledrejection', (event) => {
const error = {
message: event.reason?.message || 'Promise rejected',
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href
};
sendToErrorTracking(error);
});
7. 构建优化
7.1 Webpack优化
// webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-transform-runtime']
}
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
],
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
7.2 Vite优化
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
}
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'axios']
}
}
},
chunkSizeWarningLimit: 1000
},
server: {
hmr: true
}
});
8. 移动端优化
8.1 触摸优化
// 触摸事件优化
let touchStartTime = 0;
let touchEndTime = 0;
element.addEventListener('touchstart', (e) => {
touchStartTime = Date.now();
}, { passive: true });
element.addEventListener('touchend', (e) => {
touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
if (touchDuration < 300) {
// 快速点击处理
handleQuickTap(e);
}
}, { passive: true });
// 防止双击缩放
document.addEventListener('touchstart', (e) => {
if (e.touches.length > 1) {
e.preventDefault();
}
}, { passive: false });
let lastTouchEnd = 0;
document.addEventListener('touchend', (e) => {
const now = Date.now();
if (now - lastTouchEnd <= 300) {
e.preventDefault();
}
lastTouchEnd = now;
}, { passive: false });
8.2 响应式优化
/* 响应式图片 */
.responsive-image {
max-width: 100%;
height: auto;
}
/* 媒体查询优化 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.card {
margin-bottom: 15px;
}
}
@media (max-width: 480px) {
.container {
padding: 5px;
}
.card {
margin-bottom: 10px;
}
}
/* 视口优化 */
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
9. 测试与调试
9.1 性能测试
// 性能测试工具
class PerformanceTester {
constructor() {
this.metrics = {};
}
startTimer(name) {
this.metrics[name] = performance.now();
}
endTimer(name) {
if (this.metrics[name]) {
const duration = performance.now() - this.metrics[name];
console.log(`${name}: ${duration.toFixed(2)}ms`);
return duration;
}
}
measureMemory() {
if (performance.memory) {
console.log('Memory usage:', {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
});
}
}
}
// 使用示例
const tester = new PerformanceTester();
tester.startTimer('componentRender');
renderComponent();
tester.endTimer('componentRender');
tester.measureMemory();
9.2 调试工具
// 调试工具
const debug = {
log: (message, data) => {
if (process.env.NODE_ENV === 'development') {
console.log(`[DEBUG] ${message}`, data);
}
},
time: (label) => {
if (process.env.NODE_ENV === 'development') {
console.time(label);
}
},
timeEnd: (label) => {
if (process.env.NODE_ENV === 'development') {
console.timeEnd(label);
}
},
error: (message, error) => {
console.error(`[ERROR] ${message}`, error);
}
};
10. 总结
前端性能优化是一个系统工程,需要从多个维度进行考虑:
- 资源优化:压缩、合并、懒加载、CDN
- 代码优化:代码分割、缓存策略、渲染优化
- 网络优化:HTTP/2、预加载、预连接
- 监控分析:性能监控、错误追踪、用户行为分析
- 构建优化:打包优化、Tree Shaking、代码分割
- 移动端优化:触摸优化、响应式设计、PWA
金牧科技在前端性能优化方面拥有丰富的实践经验,如果您需要前端优化咨询或开发服务,欢迎联系我们。
相关阅读: