前端性能优化完全指南 - 我的博客
我的博客

前端性能优化完全指南

系统梳理前端代码优化与性能优化的核心策略,涵盖加载优化、渲染优化、运行时优化、构建优化等多个维度

| 3.6k 字 | 阅读时间 9 分钟

本文档系统梳理前端代码优化与性能优化的核心策略,涵盖加载优化、渲染优化、运行时优化、构建优化等多个维度。


目录

  1. 性能指标与测量工具
  2. 加载性能优化
  3. 代码优化技巧
  4. 渲染性能优化
  5. 运行时性能优化
  6. 构建与部署优化
  7. 缓存策略
  8. 监控与持续优化

一、性能指标与测量工具

1.1 核心性能指标 (Core Web Vitals)

指标全称目标值说明
LCPLargest Contentful Paint≤2.5s最大内容绘制时间,衡量首屏加载速度
FIDFirst Input Delay≤100ms首次输入延迟,衡量交互响应速度
CLSCumulative Layout Shift≤0.1累积布局偏移,衡量视觉稳定性
FCPFirst Contentful Paint≤1.8s首次内容绘制时间
TTITime to Interactive≤3.8s可交互时间
TBTTotal Blocking Time≤200ms总阻塞时间

1.2 常用测量工具

┌─────────────────────────────────────────────────────────────┐
  Lighthouse  Chrome 内置,全面评分 + 优化建议
├─────────────────────────────────────────────────────────────┤
  Chrome DevTools  Performance 面板深度分析
├─────────────────────────────────────────────────────────────┤
  WebPageTest  多地区、多网络环境测试
├─────────────────────────────────────────────────────────────┤
  Web Vitals Ext  实时查看当前页面指标
├─────────────────────────────────────────────────────────────┤
  Sentry/ARMS  线上真实用户监控 (RUM)                  │
└─────────────────────────────────────────────────────────────┘

二、加载性能优化

2.1 资源压缩与优化

代码压缩

  • JS 压缩: 使用 Terser 移除空格、注释,缩短变量名
  • CSS 压缩: 使用 CssMinimizerPlugin 优化
  • HTML 压缩: 移除空白字符和注释
// Vite 配置示例
export default defineConfig({
  build: {
    minify: 'terser',
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router'],
          ui: ['element-plus']
        }
      }
    }
  }
})

Tree Shaking 树优化

// ✅ 使用 ES Module,支持 Tree Shaking
import { debounce } from 'lodash-es'

// ❌ 避免使用 CommonJS,无法 Tree Shaking
const _ = require('lodash')

图片优化

格式压缩率适用场景
AVIF比 JPEG 小 50%+现代浏览器首选
WebP比 JPEG 小 25-35%兼容性较好的选择
JPEG基础格式通用兼容
PNG无损需要透明通道
<!-- 响应式图片 -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="示例" loading="lazy">
</picture>

2.2 代码分割与懒加载

路由懒加载

// Vue 路由懒加载
const routes = [
  {
    path: '/home',
    component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
  },
  {
    path: '/about',
    component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
  }
]

// React 路由懒加载
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./views/Home'))

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Home />
    </Suspense>
  )
}

组件懒加载

// 动态导入组件
const HeavyComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
)

图片懒加载

<!-- 原生懒加载 -->
<img src="image.jpg" loading="lazy" alt="示例">

<!-- Intersection Observer 实现 -->
<script>
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      img.classList.remove('lazy')
      imageObserver.unobserve(img)
    }
  })
})

document.querySelectorAll('img.lazy').forEach(img => {
  imageObserver.observe(img)
})
</script>

2.3 预加载策略

<!-- 预连接 - 提前建立 TCP 连接 -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

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

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

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

<!-- 预渲染整个页面 -->
<link rel="prerender" href="/next-page.html">

2.4 减少渲染阻塞

<!-- CSS 异步加载 -->
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">

<!-- JS 异步加载 -->
<script src="app.js" defer></script>    <!-- 延迟执行,保持顺序 -->
<script src="analytics.js" async></script>  <!-- 异步加载,无序执行 -->

<!-- 关键 CSS 内联 -->
<style>
  /* 首屏关键样式 */
  .hero { ... }
  .header { ... }
</style>

2.5 CDN 加速

// 静态资源 CDN 配置
export default defineConfig({
  base: 'https://cdn.example.com/',
  build: {
    assetsDir: 'static',
    rollupOptions: {
      output: {
        entryFileNames: 'js/[name]-[hash].js',
        chunkFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          return `assets/[name]-[hash][extname]`
        }
      }
    }
  }
})

三、代码优化技巧

3.1 JavaScript 代码优化

减少 DOM 操作

// ❌ 低效:多次操作 DOM
for (let i = 0; i < 100; i++) {
  document.getElementById('list').innerHTML += `<li>${i}</li>`
}

// ✅ 高效:批量操作,使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li')
  li.textContent = i
  fragment.appendChild(li)
}
document.getElementById('list').appendChild(fragment)

// ✅ 或使用 innerHTML 一次性插入
let html = ''
for (let i = 0; i < 100; i++) {
  html += `<li>${i}</li>`
}
document.getElementById('list').innerHTML = html

事件委托

// ❌ 低效:每个子元素绑定事件
document.querySelectorAll('.item').forEach(item => {
  item.addEventListener('click', handleClick)
})

// ✅ 高效:事件委托到父元素
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.classList.contains('item')) {
    handleClick(e)
  }
})

防抖与节流

// 防抖 - 延迟执行,适用于搜索输入
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// 节流 - 控制执行频率,适用于滚动、resize
function throttle(fn, limit) {
  let inThrottle = false
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args)
      inThrottle = true
      setTimeout(() => inThrottle = false, limit)
    }
  }
}

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

数据结构优化

// ✅ 使用 Map 替代 Object 进行高频查找
const map = new Map()
map.set('key', value)
const value = map.get('key')  // O(1)

// ✅ 使用 Set 进行数组去重
const unique = [...new Set(array)]

// ✅ 缓存计算结果
const memoize = (fn) => {
  const cache = new Map()
  return (...args) => {
    const key = JSON.stringify(args)
    if (cache.has(key)) return cache.get(key)
    const result = fn(...args)
    cache.set(key, result)
    return result
  }
}

循环优化

// ✅ 缓存数组长度
for (let i = 0, len = array.length; i < len; i++) {
  // ...
}

// ✅ 倒序循环(更快)
for (let i = array.length; i--;) {
  // ...
}

// ✅ 使用 for 循环替代 forEach(性能更好)
for (let i = 0; i < array.length; i++) {
  process(array[i])
}

3.2 CSS 代码优化

选择器优化

/* ❌ 低效:深层嵌套选择器 */
div.container ul li a span { }

/* ✅ 高效:使用单一类名 */
.nav-link { }

/* ✅ 避免通配符选择器 */
/* ❌ * { margin: 0; } */
/* ✅ 具体选择器 */
body, h1, h2, p { margin: 0; }

避免触发重排

/* ❌ 触发重排(Reflow) */
.element {
  width: 100px;
  height: 100px;
  margin: 10px;
  padding: 20px;
}

/* ✅ 使用 transform 和 opacity(仅触发重绘或合成) */
.element {
  transform: translateX(100px) scale(1.2);
  opacity: 0.8;
  will-change: transform;  /* 提示浏览器优化 */
}

CSS 动画优化

/* ✅ 使用 CSS 动画替代 JS 动画 */
@keyframes slideIn {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

.animated {
  animation: slideIn 0.3s ease-out;
  /* 确保动画元素脱离文档流 */
  position: absolute;
  /* 使用 GPU 加速 */
  transform: translateZ(0);
}

字体加载优化

@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;  /* 先显示后备字体,加载后替换 */
  font-weight: 400;
  font-style: normal;
}

body {
  font-family: 'CustomFont', system-ui, sans-serif;
}

3.3 HTML 优化

<!-- ✅ 压缩 HTML -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <!-- 视口设置 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 预连接 -->
  <link rel="preconnect" href="https://cdn.example.com">
  <!-- 关键 CSS 内联 -->
  <style>/* 关键样式 */</style>
  <!-- 异步加载非关键 CSS -->
  <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>
<body>
  <!-- 内容
  
  <!-- 延迟加载 JS -->
  <script src="app.js" defer></script>
</body>
</html>

四、渲染性能优化

4.1 减少重排与重绘

// ❌ 多次读取/写入导致多次重排
const height = element.offsetHeight  // 读取
element.style.height = (height + 10) + 'px'  // 写入
const width = element.offsetWidth  // 读取(强制同步布局)
element.style.width = (width + 10) + 'px'  // 写入

// ✅ 批量读取,批量写入
const height = element.offsetHeight
const width = element.offsetWidth
requestAnimationFrame(() => {
  element.style.height = (height + 10) + 'px'
  element.style.width = (width + 10) + 'px'
})

// ✅ 使用 CSS 类批量修改
const classNames = ['expanded', 'active', 'visible']
element.classList.add(...classNames)

4.2 虚拟列表

// 虚拟列表核心思想:只渲染视口内的元素
function VirtualList({ items, itemHeight, height }) {
  const [scrollTop, setScrollTop] = useState(0)
  
  const visibleCount = Math.ceil(height / itemHeight)
  const startIndex = Math.floor(scrollTop / itemHeight)
  const endIndex = Math.min(startIndex + visibleCount, items.length)
  
  const visibleItems = items.slice(startIndex, endIndex)
  const offsetY = startIndex * itemHeight
  
  return (
    <div 
      style={{ height, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: items.length * itemHeight }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems.map(item => (
            <div key={item.id} style={{ height: itemHeight }}>
              {item.content}
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

4.3 虚拟 DOM 优化

// Vue 优化
// ✅ v-for 使用 key
<template>
  <div v-for="item in list" :key="item.id">{{ item.name }}</div>
</template>

// ✅ v-show 替代频繁切换的 v-if
<template>
  <div v-show="isVisible">内容</div>
</template>

// ✅ computed 缓存计算属性
<script>
export default {
  computed: {
    filteredList() {
      return this.list.filter(item => item.active)
    }
  }
}
</script>

// React 优化
// ✅ React.memo 缓存组件
const ListItem = React.memo(({ item }) => {
  return <div>{item.name}</div>
})

// ✅ useMemo 缓存计算结果
const filteredList = useMemo(() => {
  return list.filter(item => item.active)
}, [list])

// ✅ useCallback 缓存函数
const handleClick = useCallback((id) => {
  console.log(id)
}, [])

4.4 分层与合成

/* 使用 will-change 提示浏览器优化 */
.animated-element {
  will-change: transform, opacity;
}

/* 动画结束后移除 will-change */
.animated-element.animation-complete {
  will-change: auto;
}

/* 创建新的合成层 */
.gpu-layer {
  transform: translateZ(0);
  backface-visibility: hidden;
}

五、运行时性能优化

5.1 内存管理

// ✅ 清除事件监听器
useEffect(() => {
  const handleScroll = () => { /* ... */ }
  window.addEventListener('scroll', handleScroll)
  
  return () => {
    window.removeEventListener('scroll', handleScroll)
  }
}, [])

// ✅ 清除定时器
useEffect(() => {
  const timer = setInterval(() => { /* ... */ }, 1000)
  
  return () => {
    clearInterval(timer)
  }
}, [])

// ✅ 使用 WeakMap 避免内存泄漏
const cache = new WeakMap()
function process(obj) {
  if (!cache.has(obj)) {
    cache.set(obj, heavyComputation(obj))
  }
  return cache.get(obj)
}

// ✅ 手动释放大对象
function processLargeData() {
  let largeData = fetchLargeData()
  // 处理数据...
  const result = process(largeData)
  largeData = null  // 释放引用
  return result
}

5.2 Web Worker

// worker.js
self.onmessage = function(e) {
  const data = e.data
  // 执行耗时计算
  const result = heavyComputation(data)
  self.postMessage(result)
}

// main.js
const worker = new Worker('worker.js')

worker.postMessage(largeData)

worker.onmessage = function(e) {
  const result = e.data
  // 处理结果
}

// 使用 Comlink 简化通信
import * as Comlink from 'comlink'

const WorkerClass = Comlink.wrap(new Worker('./worker.js'))
const instance = await new WorkerClass()
const result = await instance.process(data)

5.3 任务分解

// ✅ 使用 requestIdleCallback 执行低优先级任务
requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift()
    task()
  }
  
  if (tasks.length > 0) {
    requestIdleCallback(processTasks)
  }
})

// ✅ 使用 requestAnimationFrame 分解渲染任务
function processChunk(items, processItem, chunkSize = 10) {
  let index = 0
  
  function process() {
    const end = Math.min(index + chunkSize, items.length)
    
    for (let i = index; i < end; i++) {
      processItem(items[i])
    }
    
    index = end
    
    if (index < items.length) {
      requestAnimationFrame(process)
    }
  }
  
  requestAnimationFrame(process)
}

5.4 长任务优化

// ✅ 使用 Scheduler 库调度任务
import { unstable_scheduleCallback } from 'scheduler'

unstable_scheduleCallback(() => {
  // 低优先级任务
})

// ✅ React 18 并发特性
import { startTransition } from 'react'

function handleChange(value) {
  // 紧急更新
  setInputValue(value)
  
  // 非紧急更新
  startTransition(() => {
    setSearchResults(search(value))
  })
}

六、构建与部署优化

6.1 构建优化

// Vite 优化配置
export default defineConfig({
  build: {
    // 代码分割
    rollupOptions: {
      output: {
        manualChunks: {
          // 第三方库打包到一起
          vendor: ['vue', 'vue-router', 'pinia'],
          // UI 组件库单独打包
          ui: ['element-plus'],
          // 工具库
          utils: ['lodash-es', 'dayjs']
        },
        // 资源文件名添加 hash
        entryFileNames: 'js/[name]-[hash].js',
        chunkFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(assetInfo.name)) {
            return 'images/[name]-[hash][extname]'
          }
          if (/\.css$/.test(assetInfo.name)) {
            return 'css/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        }
      }
    },
    // 压缩
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    // 资源内联阈值
    assetsInlineLimit: 4096,
    // 开启 CSS 代码分割
    cssCodeSplit: true,
    // 预渲染
    ssr: false
  },
  // 优化依赖预构建
  optimizeDeps: {
    include: ['vue', 'vue-router', 'pinia', 'element-plus']
  }
})

6.2 Gzip/Brotli 压缩

# Nginx 配置
server {
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    
    # Brotli 压缩(效果更好)
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json application/javascript;
}

6.3 HTTP/2 与 HTTP/3

# HTTP/2 配置
server {
    listen 443 ssl http2;
    # ...
}

# HTTP/3 配置
server {
    listen 443 quic reuseport;
    listen 443 ssl;
    
    ssl_protocols TLSv1.3;
    add_header Alt-Svc 'h3=":443"; ma=86400';
    # ...
}

6.4 SSR 与 SSG

// Next.js SSR
export async function getServerSideProps(context) {
  const data = await fetchData()
  return {
    props: { data }
  }
}

// Next.js SSG
export async function getStaticProps() {
  const data = await fetchData()
  return {
    props: { data },
    revalidate: 60  // ISR 增量静态再生
  }
}

// Nuxt.js SSR
<script setup>
const { data } = await useFetch('/api/data')
</script>

七、缓存策略

7.1 HTTP 缓存

# 强缓存 - 长期缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# 协商缓存 - HTML 文件
location ~* \.html$ {
    add_header Cache-Control "no-cache";
}

# API 响应缓存
location /api/ {
    add_header Cache-Control "private, max-age=300";
}

7.2 Service Worker

// service-worker.js
const CACHE_NAME = 'app-v1'
const urlsToCache = [
  '/',
  '/styles.css',
  '/app.js',
  '/icon.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.status === 200) {
              const responseClone = response.clone()
              caches.open(CACHE_NAME)
                .then(cache => cache.put(event.request, responseClone))
            }
            return response
          })
      })
  )
})

八、监控与持续优化

8.1 性能监控

// Web Vitals 监控
import { onCLS, onFID, onLCP } from 'web-vitals'

onCLS((metric) => {
  console.log('CLS:', metric)
  // 上报到监控系统
  sendToAnalytics('CLS', metric)
})

onFID((metric) => {
  console.log('FID:', metric)
  sendToAnalytics('FID', metric)
})

onLCP((metric) => {
  console.log('LCP:', metric)
  sendToAnalytics('LCP', metric)
})

8.2 错误监控

// 全局错误捕获
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error)
  sendToSentry(event.error)
})

// Promise 错误捕获
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled rejection:', event.reason)
  sendToSentry(event.reason)
})

8.3 性能优化流程

┌─────────────────────────────────────────────────────────────┐
│  1. 基准测试       │  使用 Lighthouse 获取基准分数        │
├─────────────────────────────────────────────────────────────┤
│  2. 识别瓶颈       │  分析 Performance 面板,找到性能瓶颈   │
├─────────────────────────────────────────────────────────────┤
│  3. 应用优化       │  根据瓶颈类型应用对应优化策略       │
├─────────────────────────────────────────────────────────────┤
│  4. 验证效果       │  重新测试,对比优化前后的指标变化   │
├─────────────────────────────────────────────────────────────┤
│  5. 持续监控       │  部署后持续监控,及时发现问题       │
└─────────────────────────────────────────────────────────────┘

结语

前端性能优化是一个持续的过程,需要结合项目实际情况选择合适的优化策略。建议:

  1. 先测量后优化:使用性能工具获取基准数据,避免盲目优化
  2. 优先级排序:优先解决影响用户体验最大的性能问题
  3. 渐进式优化:从小处着手,逐步优化,每次优化后验证效果
  4. 持续监控:建立性能监控体系,及时发现性能退化

评论

相关文章