前端开发

Web 性能优化实战:从首屏加载到运行时流畅

系统性地讲解 Web 性能优化的各个方面,包括加载性能、渲染性能、运行时性能的优化策略。

站长 14 分钟阅读

Web 性能优化实战:从首屏加载到运行时流畅

性能优化是一个系统工程。本文从加载性能运行时性能两个维度,全面介绍 Web 性能优化策略。

一、核心性能指标

Google Core Web Vitals

指标含义目标值
LCP最大内容绘制时间< 2.5s
FID首次输入延迟< 100ms
CLS累积布局偏移< 0.1

其他重要指标

  • FCP(First Contentful Paint)— 首次内容绘制
  • TTFB(Time to First Byte)— 首字节时间
  • TTI(Time to Interactive)— 可交互时间

二、加载性能优化

1. 资源压缩

// vite.config.js
export default {
  build: {
    minify: 'terser',
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          markdown: ['react-markdown', 'remark-gfm'],
        }
      }
    }
  }
}

2. 图片优化

图片通常是页面最大的资源,优化收益最高:

<!-- 使用 WebP 格式 -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="描述" loading="lazy">
</picture>

<!-- 响应式图片 -->
<img
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px"
  src="medium.jpg"
  alt="描述"
>

3. 代码分割

// 路由级别懒加载
const BlogList = React.lazy(() => import('./pages/BlogList'))
const ArticleDetail = React.lazy(() => import('./pages/ArticleDetail'))

// 使用 Suspense 包裹
<Suspense fallback={<Loading />}>
  <Routes>
    <Route path="/blog" element={<BlogList />} />
    <Route path="/article/:id" element={<ArticleDetail />} />
  </Routes>
</Suspense>

4. 预加载与预获取

<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接 -->
<link rel="preconnect" href="//api.example.com">

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

三、渲染性能优化

1. 减少重排和重绘

// ❌ 触发多次重排
element.style.width = '100px'
element.style.height = '200px'
element.style.margin = '10px'

// ✅ 使用 class 一次性修改
element.classList.add('updated')

// ✅ 使用 transform 代替 top/left(不触发重排)
.animate {
  transform: translateX(100px)  /* 而不是 left: 100px */
}

2. 虚拟列表

当列表数据量很大时,只渲染可见区域的元素:

import { useVirtual } from 'react-virtual'

function BigList({ items }) {
  const rowVirtualizer = useVirtual({
    size: items.length,
    parentRef: listRef,
    estimateSize: () => 60,
  })

  return (
    <div ref={listRef} style={{ height: 600, overflow: 'auto' }}>
      <div style={{ height: rowVirtualizer.totalSize }}>
        {rowVirtualizer.virtualItems.map(vItem => (
          <div key={vItem.index} style={{
            position: 'absolute',
            top: vItem.start,
            height: vItem.size,
          }}>
            {items[vItem.index].name}
          </div>
        ))}
      </div>
    </div>
  )
}

3. 防抖与节流

// 防抖:延迟执行,适合搜索框
function debounce(fn, delay) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
}

// 节流:固定频率执行,适合滚动事件
function throttle(fn, interval) {
  let lastTime = 0
  return (...args) => {
    const now = Date.now()
    if (now - lastTime >= interval) {
      fn(...args)
      lastTime = now
    }
  }
}

// 使用
const handleSearch = debounce(search, 300)
const handleScroll = throttle(onScroll, 16) // ~60fps

四、运行时性能优化

1. React 性能

// 使用 React.memo 避免不必要重渲染
const Item = React.memo(({ data }) => {
  return <div>{data.name}</div>
})

// 使用 useMemo 缓存计算结果
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
)

// 使用 useCallback 缓存函数
const handleClick = useCallback((id) => {
  setSelected(id)
}, [])

2. Web Worker 处理密集计算

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

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data.data)
  self.postMessage(result)
}

3. requestAnimationFrame

// 动画使用 rAF 而不是 setInterval
function animate() {
  // 更新动画
  requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

五、监控与分析

Performance Observer

// 监控 LCP
new PerformanceObserver((list) => {
  const entries = list.getEntries()
  const lastEntry = entries[entries.length - 1]
  console.log('LCP:', lastEntry.startTime)
}).observe({ type: 'largest-contentful-paint', buffered: true })

// 监控 CLS
new PerformanceObserver((list) => {
  let cls = 0
  for (const entry of list.getEntries()) {
    cls += entry.value
  }
  console.log('CLS:', cls)
}).observe({ type: 'layout-shift', buffered: true })

总结

性能优化是一个持续的过程:

  1. 先测量 — 使用 Lighthouse、Chrome DevTools 分析瓶颈
  2. 定优先级 — 先优化影响最大的问题
  3. 逐步优化 — 每次优化后重新测量
  4. 持续监控 — 建立性能监控告警

记住:过早优化是万恶之源。先写出正确的代码,再在需要时优化。