性能优化方向指南

前端性能优化的主要方向和实践方案

问题

前端性能优化有哪些方向?如何系统地进行性能优化?

解答

1. 加载性能优化

资源压缩与合并

// webpack 配置示例
module.exports = {
  optimization: {
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors'
        }
      }
    },
    // 压缩
    minimize: true
  }
}

图片懒加载

// 使用 Intersection Observer 实现懒加载
function lazyLoadImages() {
  const images = document.querySelectorAll('img[data-src]')
  
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target
        img.src = img.dataset.src
        img.removeAttribute('data-src')
        observer.unobserve(img)
      }
    })
  }, {
    rootMargin: '100px' // 提前 100px 加载
  })
  
  images.forEach(img => observer.observe(img))
}

资源预加载

<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="main.js" as="script">

<!-- 预连接第三方域名 -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- 预获取下一页资源 -->
<link rel="prefetch" href="next-page.js">

2. 渲染性能优化

防抖与节流

// 防抖:延迟执行,重复触发会重置计时
function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// 节流:固定间隔执行一次
function throttle(fn, interval) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= interval) {
      lastTime = now
      fn.apply(this, args)
    }
  }
}

// 使用示例
window.addEventListener('scroll', throttle(handleScroll, 100))
input.addEventListener('input', debounce(search, 300))

减少重排重绘

// 批量修改 DOM
function batchUpdate(items) {
  // 使用 DocumentFragment
  const fragment = document.createDocumentFragment()
  items.forEach(item => {
    const li = document.createElement('li')
    li.textContent = item
    fragment.appendChild(li)
  })
  list.appendChild(fragment)
}

// 批量修改样式
function updateStyles(el) {
  // 不好:多次触发重排
  // el.style.width = '100px'
  // el.style.height = '100px'
  // el.style.margin = '10px'
  
  // 好:一次性修改
  el.style.cssText = 'width: 100px; height: 100px; margin: 10px;'
  // 或使用 class
  el.className = 'updated-style'
}

虚拟列表

// 简易虚拟列表实现
class VirtualList {
  constructor(container, itemHeight, totalCount, renderItem) {
    this.container = container
    this.itemHeight = itemHeight
    this.totalCount = totalCount
    this.renderItem = renderItem
    
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight)
    this.startIndex = 0
    
    this.init()
  }
  
  init() {
    // 创建占位元素
    this.placeholder = document.createElement('div')
    this.placeholder.style.height = `${this.totalCount * this.itemHeight}px`
    
    // 创建内容容器
    this.content = document.createElement('div')
    this.content.style.position = 'relative'
    
    this.container.appendChild(this.placeholder)
    this.placeholder.appendChild(this.content)
    
    this.container.addEventListener('scroll', () => this.onScroll())
    this.render()
  }
  
  onScroll() {
    const scrollTop = this.container.scrollTop
    const newStartIndex = Math.floor(scrollTop / this.itemHeight)
    
    if (newStartIndex !== this.startIndex) {
      this.startIndex = newStartIndex
      this.render()
    }
  }
  
  render() {
    // 只渲染可见区域 + 缓冲区
    const buffer = 5
    const start = Math.max(0, this.startIndex - buffer)
    const end = Math.min(this.totalCount, this.startIndex + this.visibleCount + buffer)
    
    this.content.innerHTML = ''
    this.content.style.transform = `translateY(${start * this.itemHeight}px)`
    
    for (let i = start; i < end; i++) {
      const item = this.renderItem(i)
      item.style.height = `${this.itemHeight}px`
      this.content.appendChild(item)
    }
  }
}

3. 网络性能优化

请求优化

// 请求合并
async function batchRequest(ids) {
  // 不好:多个请求
  // const results = await Promise.all(ids.map(id => fetch(`/api/item/${id}`)))
  
  // 好:合并为一个请求
  const result = await fetch('/api/items', {
    method: 'POST',
    body: JSON.stringify({ ids })
  })
  return result.json()
}

// 请求缓存
const cache = new Map()

async function fetchWithCache(url, ttl = 60000) {
  const cached = cache.get(url)
  if (cached && Date.now() - cached.time < ttl) {
    return cached.data
  }
  
  const data = await fetch(url).then(r => r.json())
  cache.set(url, { data, time: Date.now() })
  return data
}

4. 代码层面优化

// 避免内存泄漏
class Component {
  constructor() {
    this.handler = this.handleClick.bind(this)
    document.addEventListener('click', this.handler)
  }
  
  // 组件销毁时移除监听
  destroy() {
    document.removeEventListener('click', this.handler)
  }
  
  handleClick() {}
}

// 使用 Web Worker 处理耗时任务
// worker.js
self.onmessage = function(e) {
  const result = heavyComputation(e.data)
  self.postMessage(result)
}

// main.js
const worker = new Worker('worker.js')
worker.postMessage(data)
worker.onmessage = (e) => {
  console.log('计算结果:', e.data)
}

5. 构建优化

// vite.config.js
export default {
  build: {
    // 代码分割
    rollupOptions: {
      output: {
        manualChunks: {
          vue: ['vue', 'vue-router'],
          utils: ['lodash-es', 'dayjs']
        }
      }
    },
    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true // 移除 console
      }
    }
  }
}

关键点

  • 加载优化:压缩资源、懒加载、预加载、使用 CDN、合理缓存
  • 渲染优化:减少重排重绘、使用虚拟列表、防抖节流
  • 网络优化:减少请求数、使用 HTTP/2、开启 Gzip
  • 代码优化:避免内存泄漏、耗时任务用 Web Worker
  • 构建优化:Tree Shaking、代码分割、按需加载