Skip to content

前端性能优化实战指南

性能优化是前端开发中的重要主题。本文将从多个维度深入探讨前端性能优化的策略和实践。

性能指标概述

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 }]
      }
    }
  }
}

总结

前端性能优化是一个系统性工程,需要从多个维度进行考虑:

  1. 加载性能:资源优化、网络优化、缓存策略
  2. 运行时性能:JavaScript 优化、DOM 操作优化、CSS 优化
  3. 框架优化:利用框架特性进行针对性优化
  4. 监控测量:建立性能监控体系,持续优化

性能优化是一个持续的过程,需要根据实际业务场景和用户需求,选择合适的优化策略。记住,过早的优化是万恶之源,应该先测量,再优化,最后验证效果。

用代码构建未来,让技术改变世界 🚀 | 专注前端技术,分享开发经验