Appearance
前端性能优化实战指南
性能优化是前端开发中的重要主题。本文将从多个维度深入探讨前端性能优化的策略和实践。
性能指标概述
Core Web Vitals
Google 提出的核心网页指标:
- LCP (Largest Contentful Paint):最大内容绘制时间 < 2.5s
- FID (First Input Delay):首次输入延迟 < 100ms
- CLS (Cumulative Layout Shift):累积布局偏移 < 0.1
其他重要指标
- FCP (First Contentful Paint):首次内容绘制
- TTI (Time to Interactive):可交互时间
- FMP (First Meaningful Paint):首次有意义绘制
加载性能优化
1. 资源优化
图片优化
html
<!-- 响应式图片 -->
<picture>
<source media="(min-width: 800px)" srcset="large.webp" type="image/webp">
<source media="(min-width: 800px)" srcset="large.jpg">
<source srcset="small.webp" type="image/webp">
<img src="small.jpg" alt="描述" loading="lazy">
</picture>
<!-- 现代图片格式 -->
<img src="image.avif" alt="描述"
onerror="this.onerror=null; this.src='image.webp'">
CSS 优化
css
/* 关键 CSS 内联 */
<style>
/* 首屏关键样式 */
.header { /* ... */ }
.hero { /* ... */ }
</style>
/* 非关键 CSS 异步加载 */
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="non-critical.css"></noscript>
JavaScript 优化
html
<!-- 代码分割 -->
<script type="module">
// 动态导入
const { heavyFunction } = await import('./heavy-module.js')
heavyFunction()
</script>
<!-- 预加载关键资源 -->
<link rel="modulepreload" href="./critical-module.js">
<!-- 延迟非关键脚本 -->
<script src="analytics.js" defer></script>
2. 网络优化
HTTP/2 和 HTTP/3
javascript
// 利用 HTTP/2 多路复用
// 避免域名分片,使用单一域名
const API_BASE = 'https://api.example.com'
// 服务器推送关键资源
// 在服务器配置中启用
资源预加载
html
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<!-- 资源预加载 -->
<link rel="preload" href="hero-image.jpg" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 页面预取 -->
<link rel="prefetch" href="/next-page.html">
3. 缓存策略
Service Worker 缓存
javascript
// sw.js
const CACHE_NAME = 'app-v1'
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/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).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
})
})
)
})
HTTP 缓存头
javascript
// Express.js 示例
app.use('/static', express.static('public', {
maxAge: '1y', // 静态资源长期缓存
etag: true,
lastModified: true
}))
// 动态内容缓存
app.get('/api/data', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=300', // 5分钟缓存
'ETag': generateETag(data)
})
res.json(data)
})
运行时性能优化
1. JavaScript 性能
避免长任务
javascript
// ❌ 长任务阻塞主线程
function processLargeArray(items) {
for (let i = 0; i < items.length; i++) {
// 复杂处理
processItem(items[i])
}
}
// ✅ 时间切片处理
function processLargeArrayAsync(items, callback) {
let index = 0
function processChunk() {
const start = performance.now()
while (index < items.length && performance.now() - start < 5) {
processItem(items[index])
index++
}
if (index < items.length) {
// 让出控制权,下一帧继续处理
requestIdleCallback(processChunk)
} else {
callback()
}
}
processChunk()
}
// 使用 Web Workers 处理密集计算
const worker = new Worker('heavy-computation.js')
worker.postMessage({ data: largeDataSet })
worker.onmessage = event => {
const result = event.data
updateUI(result)
}
内存管理
javascript
// 避免内存泄漏
class Component {
constructor() {
this.handleResize = this.handleResize.bind(this)
this.timerId = null
}
mount() {
window.addEventListener('resize', this.handleResize)
this.timerId = setInterval(this.update, 1000)
}
unmount() {
// 清理事件监听器
window.removeEventListener('resize', this.handleResize)
// 清理定时器
if (this.timerId) {
clearInterval(this.timerId)
this.timerId = null
}
// 清理引用
this.data = null
}
handleResize() {
// 防抖处理
clearTimeout(this.resizeTimer)
this.resizeTimer = setTimeout(() => {
this.updateLayout()
}, 100)
}
}
// 使用 WeakMap 避免循环引用
const componentData = new WeakMap()
function attachData(element, data) {
componentData.set(element, data)
}
function getData(element) {
return componentData.get(element)
}
2. DOM 操作优化
批量 DOM 操作
javascript
// ❌ 频繁 DOM 操作
function updateList(items) {
const list = document.getElementById('list')
list.innerHTML = '' // 触发重排
items.forEach(item => {
const li = document.createElement('li')
li.textContent = item.name
list.appendChild(li) // 每次都触发重排
})
}
// ✅ 批量操作
function updateListOptimized(items) {
const list = document.getElementById('list')
const fragment = document.createDocumentFragment()
items.forEach(item => {
const li = document.createElement('li')
li.textContent = item.name
fragment.appendChild(li) // 在内存中操作
})
list.innerHTML = ''
list.appendChild(fragment) // 一次性插入
}
// 使用 requestAnimationFrame 优化动画
function animateElement(element, targetX) {
let currentX = 0
function animate() {
if (currentX < targetX) {
currentX += 2
element.style.transform = `translateX(${currentX}px)`
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
}
虚拟滚动
javascript
class VirtualList {
constructor(container, items, itemHeight) {
this.container = container
this.items = items
this.itemHeight = itemHeight
this.visibleCount = Math.ceil(container.clientHeight / itemHeight)
this.startIndex = 0
this.init()
}
init() {
this.container.style.height = `${this.items.length * this.itemHeight}px`
this.container.style.position = 'relative'
this.container.addEventListener('scroll', this.handleScroll.bind(this))
this.render()
}
handleScroll() {
const scrollTop = this.container.scrollTop
const newStartIndex = Math.floor(scrollTop / this.itemHeight)
if (newStartIndex !== this.startIndex) {
this.startIndex = newStartIndex
this.render()
}
}
render() {
const endIndex = Math.min(
this.startIndex + this.visibleCount + 1,
this.items.length
)
const visibleItems = this.items.slice(this.startIndex, endIndex)
this.container.innerHTML = visibleItems
.map((item, index) => {
const actualIndex = this.startIndex + index
const top = actualIndex * this.itemHeight
return `
<div style="position: absolute; top: ${top}px; height: ${this.itemHeight}px;">
${item.content}
</div>
`
})
.join('')
}
}
3. CSS 性能优化
避免强制重排
css
/* ❌ 触发重排的属性 */
.element {
width: 100px;
height: 100px;
top: 10px;
left: 10px;
}
/* ✅ 使用 transform 和 opacity */
.element {
transform: translateX(10px) translateY(10px) scale(1.1);
opacity: 0.8;
will-change: transform, opacity;
}
/* 合理使用 will-change */
.animating {
will-change: transform;
}
.animating.finished {
will-change: auto;
}
CSS 包含
css
/* 使用 contain 属性优化渲染 */
.card {
contain: layout style paint;
}
.sidebar {
contain: layout;
}
.content {
contain: paint;
}
框架特定优化
React 优化
javascript
// 使用 React.memo 避免不必要的重渲染
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
return (
<div>
{data.map(item => (
<Item key={item.id} item={item} onUpdate={onUpdate} />
))}
</div>
)
}, (prevProps, nextProps) => {
// 自定义比较函数
return prevProps.data.length === nextProps.data.length &&
prevProps.data.every((item, index) =>
item.id === nextProps.data[index].id
)
})
// 使用 useMemo 和 useCallback
function Component({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item => item.category === filter)
}, [items, filter])
const handleClick = useCallback((id) => {
// 处理点击
}, [])
return (
<div>
{filteredItems.map(item => (
<Item key={item.id} item={item} onClick={handleClick} />
))}
</div>
)
}
// 代码分割
const LazyComponent = React.lazy(() => import('./HeavyComponent'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
)
}
Vue 优化
javascript
// 使用 v-memo 优化列表渲染
<template>
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">
{{ item.name }}
</div>
</template>
// 异步组件
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
// 使用 shallowRef 优化大型对象
import { shallowRef, triggerRef } from 'vue'
const state = shallowRef({
largeArray: new Array(10000).fill(0).map((_, i) => ({ id: i, value: i }))
})
// 更新时手动触发
function updateArray() {
state.value.largeArray.push({ id: Date.now(), value: Math.random() })
triggerRef(state)
}
监控和测量
Performance API
javascript
// 测量关键指标
function measurePerformance() {
// FCP
const fcpEntry = performance.getEntriesByName('first-contentful-paint')[0]
if (fcpEntry) {
console.log('FCP:', fcpEntry.startTime)
}
// LCP
new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', lastEntry.startTime)
}).observe({ entryTypes: ['largest-contentful-paint'] })
// FID
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('FID:', entry.processingStart - entry.startTime)
}
}).observe({ entryTypes: ['first-input'] })
// CLS
let clsValue = 0
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value
}
}
console.log('CLS:', clsValue)
}).observe({ entryTypes: ['layout-shift'] })
}
// 自定义性能标记
performance.mark('component-start')
// ... 组件渲染逻辑
performance.mark('component-end')
performance.measure('component-render', 'component-start', 'component-end')
const measure = performance.getEntriesByName('component-render')[0]
console.log('Component render time:', measure.duration)
性能预算
javascript
// webpack.config.js
module.exports = {
performance: {
maxAssetSize: 250000, // 250KB
maxEntrypointSize: 250000,
hints: 'warning'
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
// Lighthouse CI 配置
// lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000'],
numberOfRuns: 3
},
assert: {
assertions: {
'categories:performance': ['warn', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }]
}
}
}
}
总结
前端性能优化是一个系统性工程,需要从多个维度进行考虑:
- 加载性能:资源优化、网络优化、缓存策略
- 运行时性能:JavaScript 优化、DOM 操作优化、CSS 优化
- 框架优化:利用框架特性进行针对性优化
- 监控测量:建立性能监控体系,持续优化
性能优化是一个持续的过程,需要根据实际业务场景和用户需求,选择合适的优化策略。记住,过早的优化是万恶之源,应该先测量,再优化,最后验证效果。