前端性能优化完全指南
前端性能优化是提升用户体验的关键。本指南将从多个维度介绍现代前端性能优化的策略、工具和最佳实践。
🎯 性能优化概述
性能指标体系
Core Web Vitals
javascript
// 核心性能指标
const webVitals = {
LCP: 'Largest Contentful Paint', // 最大内容绘制 < 2.5s
FID: 'First Input Delay', // 首次输入延迟 < 100ms
CLS: 'Cumulative Layout Shift' // 累积布局偏移 < 0.1
}
// 其他重要指标
const otherMetrics = {
FCP: 'First Contentful Paint', // 首次内容绘制 < 1.8s
TTI: 'Time to Interactive', // 可交互时间 < 3.8s
TBT: 'Total Blocking Time', // 总阻塞时间 < 200ms
SI: 'Speed Index' // 速度指数 < 3.4s
}
性能优化金字塔
mermaid
graph TB
A[网络优化] --> B[资源优化]
B --> C[渲染优化]
C --> D[JavaScript优化]
D --> E[用户体验优化]
style A fill:#ff6b6b
style B fill:#4ecdc4
style C fill:#45b7d1
style D fill:#96ceb4
style E fill:#feca57
🌐 网络优化
HTTP 缓存策略
1. 强缓存配置
javascript
// Nginx 配置
server {
# 静态资源强缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
# HTML 文件不缓存
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
// Express.js 配置
app.use('/static', express.static('public', {
maxAge: '1y',
immutable: true
}))
2. 协商缓存
javascript
// 设置 ETag
app.get('/api/data', (req, res) => {
const data = getData()
const etag = generateETag(data)
res.set('ETag', etag)
if (req.get('If-None-Match') === etag) {
return res.status(304).end()
}
res.json(data)
})
// 设置 Last-Modified
app.get('/api/file', (req, res) => {
const filePath = getFilePath()
const stats = fs.statSync(filePath)
const lastModified = stats.mtime.toUTCString()
res.set('Last-Modified', lastModified)
if (req.get('If-Modified-Since') === lastModified) {
return res.status(304).end()
}
res.sendFile(filePath)
})
CDN 和资源分发
1. CDN 配置
javascript
// 多 CDN 配置
const cdnConfig = {
primary: 'https://cdn1.example.com',
fallback: 'https://cdn2.example.com',
local: '/static'
}
function getAssetUrl(path) {
try {
return `${cdnConfig.primary}${path}`
} catch (error) {
console.warn('Primary CDN failed, using fallback')
return `${cdnConfig.fallback}${path}`
}
}
// 预连接优化
const preconnectLinks = [
'<link rel="preconnect" href="https://fonts.googleapis.com">',
'<link rel="preconnect" href="https://api.example.com">',
'<link rel="dns-prefetch" href="https://cdn.example.com">'
]
2. 资源预加载
html
<!-- 关键资源预加载 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/main.js" as="script">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/js/page2.js">
<link rel="prefetch" href="/css/page2.css">
<!-- 预渲染下一页 -->
<link rel="prerender" href="/next-page">
📦 资源优化
代码分割策略
1. 路由级分割
javascript
// React Router 懒加载
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Contact = lazy(() => import('./pages/Contact'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
)
}
// Vue Router 懒加载
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
2. 组件级分割
javascript
// React 动态导入
import { useState, lazy, Suspense } from 'react'
const HeavyComponent = lazy(() => import('./HeavyComponent'))
function App() {
const [showHeavy, setShowHeavy] = useState(false)
return (
<div>
<button onClick={() => setShowHeavy(true)}>
Load Heavy Component
</button>
{showHeavy && (
<Suspense fallback={<div>Loading heavy component...</div>}>
<HeavyComponent />
</Suspense>
)}
</div>
)
}
// Vue 3 异步组件
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
Bundle 优化
1. Webpack 配置优化
javascript
// webpack.config.js
const path = require('path')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5
}
}
},
usedExports: true,
sideEffects: false
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE ? 'server' : 'disabled'
})
]
}
2. Vite 配置优化
javascript
// vite.config.js
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['antd', '@ant-design/icons'],
utils: ['lodash', 'dayjs']
}
}
},
chunkSizeWarningLimit: 1000
},
plugins: [
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true
})
]
})
图片优化
1. 现代图片格式
html
<!-- WebP 格式支持 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.avif" type="image/avif">
<img src="image.jpg" alt="Description" loading="lazy">
</picture>
<!-- 响应式图片 -->
<img
srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 480px) 100vw, (max-width: 800px) 50vw, 25vw"
src="medium.jpg"
alt="Responsive image"
loading="lazy"
>
2. 图片懒加载
javascript
// Intersection Observer 懒加载
class LazyImageLoader {
constructor() {
this.imageObserver = new IntersectionObserver(
this.handleIntersection.bind(this),
{ rootMargin: '50px' }
)
this.init()
}
init() {
const lazyImages = document.querySelectorAll('img[data-src]')
lazyImages.forEach(img => this.imageObserver.observe(img))
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
img.removeAttribute('data-src')
this.imageObserver.unobserve(img)
}
})
}
}
new LazyImageLoader()
⚡ 渲染优化
关键渲染路径优化
1. CSS 优化
css
/* 关键 CSS 内联 */
<style>
/* 首屏关键样式 */
.header { display: flex; }
.main { min-height: 100vh; }
</style>
/* 非关键 CSS 异步加载 */
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
javascript
// CSS-in-JS 优化
import styled from 'styled-components'
// 避免在渲染函数中创建样式组件
const StyledButton = styled.button`
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
padding: 10px 20px;
`
// 使用 CSS 变量减少重新计算
const DynamicComponent = styled.div`
--primary-color: ${props => props.color};
background: var(--primary-color);
`
2. JavaScript 优化
javascript
// 避免长任务阻塞
function processLargeData(data) {
return new Promise(resolve => {
const chunks = chunkArray(data, 1000)
let result = []
function processChunk(index) {
if (index >= chunks.length) {
resolve(result)
return
}
// 处理当前块
result = result.concat(processChunk(chunks[index]))
// 让出控制权给浏览器
setTimeout(() => processChunk(index + 1), 0)
}
processChunk(0)
})
}
// 使用 requestIdleCallback
function performWork(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift()
task()
}
if (tasks.length > 0) {
requestIdleCallback(performWork)
}
}
requestIdleCallback(performWork)
虚拟滚动
javascript
// React 虚拟滚动实现
import { useState, useEffect, useMemo } from 'react'
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0)
const visibleItems = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
)
return items.slice(startIndex, endIndex).map((item, index) => ({
...item,
index: startIndex + index
}))
}, [items, itemHeight, containerHeight, scrollTop])
const totalHeight = items.length * itemHeight
const offsetY = Math.floor(scrollTop / itemHeight) * itemHeight
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map(item => (
<div
key={item.id}
style={{ height: itemHeight }}
>
{item.content}
</div>
))}
</div>
</div>
</div>
)
}
🔧 JavaScript 优化
内存管理
1. 内存泄漏预防
javascript
// 正确清理事件监听器
class Component {
constructor() {
this.handleResize = this.handleResize.bind(this)
}
componentDidMount() {
window.addEventListener('resize', this.handleResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize)
}
handleResize() {
// 处理逻辑
}
}
// 清理定时器
class Timer {
constructor() {
this.timerId = null
}
start() {
this.timerId = setInterval(() => {
// 定时任务
}, 1000)
}
stop() {
if (this.timerId) {
clearInterval(this.timerId)
this.timerId = null
}
}
}
// 使用 WeakMap 避免内存泄漏
const componentData = new WeakMap()
class Component {
constructor() {
componentData.set(this, { data: 'some data' })
}
getData() {
return componentData.get(this)
}
}
2. 对象池模式
javascript
// 对象池减少 GC 压力
class ObjectPool {
constructor(createFn, resetFn, initialSize = 10) {
this.createFn = createFn
this.resetFn = resetFn
this.pool = []
// 预创建对象
for (let i = 0; i < initialSize; i++) {
this.pool.push(this.createFn())
}
}
acquire() {
return this.pool.length > 0 ? this.pool.pop() : this.createFn()
}
release(obj) {
this.resetFn(obj)
this.pool.push(obj)
}
}
// 使用示例
const particlePool = new ObjectPool(
() => ({ x: 0, y: 0, vx: 0, vy: 0 }),
(particle) => {
particle.x = 0
particle.y = 0
particle.vx = 0
particle.vy = 0
}
)
计算优化
1. 防抖和节流
javascript
// 防抖函数
function debounce(func, wait, immediate = false) {
let timeout
return function executedFunction(...args) {
const later = () => {
timeout = null
if (!immediate) func.apply(this, args)
}
const callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(this, args)
}
}
// 节流函数
function throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 使用示例
const debouncedSearch = debounce(searchFunction, 300)
const throttledScroll = throttle(scrollHandler, 100)
2. Web Workers
javascript
// main.js
const worker = new Worker('worker.js')
worker.postMessage({ data: largeDataSet, operation: 'process' })
worker.onmessage = function(e) {
const result = e.data
updateUI(result)
}
// worker.js
self.onmessage = function(e) {
const { data, operation } = e.data
if (operation === 'process') {
const result = processLargeData(data)
self.postMessage(result)
}
}
function processLargeData(data) {
// 耗时计算
return data.map(item => heavyComputation(item))
}
📊 性能监控
性能指标收集
javascript
// Web Vitals 监控
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
function sendToAnalytics(metric) {
// 发送到分析服务
fetch('/analytics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
delta: metric.delta,
url: window.location.href,
timestamp: Date.now()
})
})
}
// 监控所有核心指标
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getTTFB(sendToAnalytics)
// 自定义性能监控
class PerformanceMonitor {
constructor() {
this.metrics = new Map()
this.observer = new PerformanceObserver(this.handleEntries.bind(this))
this.observer.observe({ entryTypes: ['navigation', 'resource', 'paint'] })
}
handleEntries(list) {
for (const entry of list.getEntries()) {
this.processEntry(entry)
}
}
processEntry(entry) {
switch (entry.entryType) {
case 'navigation':
this.metrics.set('domContentLoaded', entry.domContentLoadedEventEnd)
this.metrics.set('loadComplete', entry.loadEventEnd)
break
case 'paint':
this.metrics.set(entry.name, entry.startTime)
break
case 'resource':
if (entry.transferSize > 100000) { // 大于100KB的资源
console.warn(`Large resource: ${entry.name} (${entry.transferSize} bytes)`)
}
break
}
}
getMetrics() {
return Object.fromEntries(this.metrics)
}
}
const monitor = new PerformanceMonitor()
性能预算
javascript
// 性能预算配置
const performanceBudget = {
// 时间预算 (毫秒)
timing: {
FCP: 1800,
LCP: 2500,
FID: 100,
CLS: 0.1
},
// 资源预算 (字节)
resources: {
javascript: 200 * 1024, // 200KB
css: 50 * 1024, // 50KB
images: 500 * 1024, // 500KB
fonts: 100 * 1024 // 100KB
}
}
// 预算检查
function checkPerformanceBudget() {
const entries = performance.getEntriesByType('resource')
const budgetReport = {}
entries.forEach(entry => {
const type = getResourceType(entry.name)
if (!budgetReport[type]) {
budgetReport[type] = 0
}
budgetReport[type] += entry.transferSize
})
Object.keys(budgetReport).forEach(type => {
const budget = performanceBudget.resources[type]
const actual = budgetReport[type]
if (budget && actual > budget) {
console.warn(`${type} budget exceeded: ${actual} > ${budget}`)
}
})
}
💡 最佳实践
1. 性能优化策略
- 测量优先:先测量再优化,避免过早优化
- 用户体验导向:关注用户感知的性能指标
- 渐进增强:确保基础功能在所有设备上可用
- 持续监控:建立性能监控和告警机制
2. 优化优先级
- 关键渲染路径:优先优化首屏渲染
- JavaScript 执行:减少主线程阻塞
- 网络请求:优化资源加载策略
- 用户交互:提升交互响应速度
3. 团队协作
- 性能文化:建立团队性能意识
- 工具集成:在开发流程中集成性能工具
- 知识分享:定期分享性能优化经验
- 持续改进:建立性能优化的持续改进机制
通过系统性的性能优化策略,可以显著提升用户体验,提高产品竞争力。