Skip to content

前端性能优化实践

前端性能优化是提升用户体验的关键。本文将介绍前端性能优化的各个方面,包括代码分割、懒加载、缓存策略、渲染优化等最佳实践。

代码分割(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`)
  })
})

最佳实践总结

  1. 代码分割:按路由和组件进行代码分割
  2. 懒加载:图片、组件、路由都使用懒加载
  3. 缓存策略:合理使用 HTTP 缓存和浏览器存储
  4. 渲染优化:使用防抖节流,避免强制同步布局
  5. 资源优化:压缩图片,使用现代格式,优化字体加载
  6. 打包优化:Tree shaking,代码压缩,分析打包体积
  7. 性能监控:使用 Web Vitals 和 Performance API 监控性能

总结

前端性能优化是一个持续的过程,需要从多个方面入手。通过代码分割、懒加载、缓存策略、渲染优化等手段,可以显著提升应用的性能和用户体验。记住,性能优化不是一次性的工作,而是需要在开发过程中持续关注和改进。


辛田信息技术 · 内部技术分享 · 仅供学习与参考