Appearance
前端性能优化实践
前端性能优化是提升用户体验的关键。本文将介绍前端性能优化的各个方面,包括代码分割、懒加载、缓存策略、渲染优化等最佳实践。
代码分割(Code Splitting)
代码分割是将代码拆分成多个较小的块,按需加载,从而减少初始加载时间。
路由级别的代码分割
javascript
// Vue Router
const routes = [
{
path: '/home',
component: () => import('./views/Home.vue') // 懒加载
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
// React Router
const Home = lazy(() => import('./views/Home'))
const About = lazy(() => import('./views/About'))组件级别的代码分割
javascript
// Vue
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
// React
const HeavyComponent = lazy(() => import('./components/HeavyComponent'))Webpack 动态导入
javascript
// 使用魔法注释优化代码分割
import(
/* webpackChunkName: "my-chunk" */
/* webpackPrefetch: true */
'./module'
)懒加载(Lazy Loading)
图片懒加载
javascript
// 使用 Intersection Observer API
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)
}
})
})
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img)
})使用原生 loading 属性
html
<img src="image.jpg" loading="lazy" alt="描述">虚拟滚动
对于长列表,使用虚拟滚动只渲染可见区域:
vue
<template>
<VirtualList
:data-key="'id'"
:data-sources="items"
:data-component="Item"
:keeps="20"
/>
</template>缓存策略
HTTP 缓存
javascript
// 设置缓存头
app.use((req, res, next) => {
// 静态资源长期缓存
if (req.url.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
res.setHeader('Cache-Control', 'public, max-age=31536000')
}
// HTML 文件不缓存
else if (req.url.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache')
}
next()
})Service Worker 缓存
javascript
// service-worker.js
const CACHE_NAME = 'my-app-v1'
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
]
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 => response || fetch(event.request))
)
})浏览器存储
javascript
// localStorage 缓存数据
const cacheKey = 'user-data'
const cacheExpiry = 60 * 60 * 1000 // 1小时
function getCachedData() {
const cached = localStorage.getItem(cacheKey)
if (cached) {
const { data, timestamp } = JSON.parse(cached)
if (Date.now() - timestamp < cacheExpiry) {
return data
}
}
return null
}
function setCachedData(data) {
localStorage.setItem(cacheKey, JSON.stringify({
data,
timestamp: Date.now()
}))
}渲染优化
防抖和节流
javascript
// 防抖:延迟执行
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
// 节流:限制执行频率
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)使用 requestAnimationFrame
javascript
function animate() {
// 动画逻辑
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)避免强制同步布局
javascript
// ❌ 不好:强制同步布局
const width = element.offsetWidth
element.style.width = width + 10 + 'px'
const height = element.offsetHeight
// ✅ 好:批量读取和写入
const width = element.offsetWidth
const height = element.offsetHeight
element.style.width = width + 10 + 'px'资源优化
图片优化
html
<!-- 使用现代图片格式 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述">
</picture>
<!-- 响应式图片 -->
<img
srcset="image-320w.jpg 320w,
image-640w.jpg 640w,
image-1280w.jpg 1280w"
sizes="(max-width: 640px) 100vw, 640px"
src="image-640w.jpg"
alt="描述"
>字体优化
css
/* 使用 font-display: swap */
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap;
}
/* 预加载关键字体 */
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>CSS 优化
css
/* 使用 contain 属性 */
.card {
contain: layout style paint;
}
/* 使用 will-change 提示浏览器 */
.animated-element {
will-change: transform;
}打包优化
Tree Shaking
javascript
// 只导入需要的部分
import { debounce } from 'lodash-es' // ✅
import _ from 'lodash' // ❌
// 使用 ES 模块
export const util1 = () => {}
export const util2 = () => {}压缩和混淆
javascript
// webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true // 移除 console
}
}
})
]
}
}分析打包体积
javascript
// 使用 webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}性能监控
Web Vitals
javascript
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
function sendToAnalytics(metric) {
// 发送到分析服务
console.log(metric)
}
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getTTFB(sendToAnalytics)Performance API
javascript
// 测量代码执行时间
const start = performance.now()
// 执行代码
const end = performance.now()
console.log(`执行时间: ${end - start} 毫秒`)
// 资源加载时间
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('resource')
perfData.forEach(resource => {
console.log(`${resource.name}: ${resource.duration}ms`)
})
})最佳实践总结
- 代码分割:按路由和组件进行代码分割
- 懒加载:图片、组件、路由都使用懒加载
- 缓存策略:合理使用 HTTP 缓存和浏览器存储
- 渲染优化:使用防抖节流,避免强制同步布局
- 资源优化:压缩图片,使用现代格式,优化字体加载
- 打包优化:Tree shaking,代码压缩,分析打包体积
- 性能监控:使用 Web Vitals 和 Performance API 监控性能
总结
前端性能优化是一个持续的过程,需要从多个方面入手。通过代码分割、懒加载、缓存策略、渲染优化等手段,可以显著提升应用的性能和用户体验。记住,性能优化不是一次性的工作,而是需要在开发过程中持续关注和改进。