Skip to content
On this page

前端性能优化

前端性能优化是提升用户体验和网站加载速度的关键。本章将详细介绍各种前端性能优化技术和最佳实践。

资源加载优化

资源预加载策略

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

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

<!-- 资源预加载 -->
<link rel="preload" href="/critical-styles.css" as="style">
<link rel="preload" href="/hero-image.jpg" as="image">
<link rel="preload" href="/critical-font.woff2" as="font" type="font/woff2" crossorigin>

<!-- 资源预获取 -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/secondary-script.js">

JavaScript 加载优化

javascript
// 1. 使用 defer 和 async 属性
// defer: 脚本按顺序执行,在 DOM 解析完成后
// async: 脚本异步加载,加载完成后立即执行
const scriptLoader = {
  // 动态加载脚本
  loadScript: function(src, options = {}) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      
      if (options.async) script.async = true;
      if (options.defer) script.defer = true;
      
      script.onload = resolve;
      script.onerror = reject;
      
      document.head.appendChild(script);
    });
  },
  
  // 按需加载模块
  loadModule: async function(modulePath) {
    const module = await import(modulePath);
    return module;
  },
  
  // 脚本加载队列
  loadQueue: async function(scripts) {
    for (const script of scripts) {
      await this.loadScript(script);
    }
  }
};

// 2. 脚本分割和懒加载
class CodeSplitter {
  constructor() {
    this.loadedChunks = new Set();
  }
  
  // 动态导入实现懒加载
  async loadFeature(featureName) {
    if (this.loadedChunks.has(featureName)) {
      return this.loadedChunks.get(featureName);
    }
    
    let module;
    switch(featureName) {
      case 'chart':
        module = await import('./features/chart.js');
        break;
      case 'editor':
        module = await import('./features/editor.js');
        break;
      case 'gallery':
        module = await import('./features/gallery.js');
        break;
      default:
        throw new Error(`Unknown feature: ${featureName}`);
    }
    
    this.loadedChunks.set(featureName, module);
    return module;
  }
  
  // 预加载功能模块
  preloadFeature(featureName) {
    // 使用 Intersection Observer 预加载即将需要的功能
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            this.loadFeature(featureName);
            observer.unobserve(entry.target);
          }
        });
      });
      
      // 观察需要该功能的元素
      const featureTrigger = document.querySelector(`[data-feature="${featureName}"]`);
      if (featureTrigger) {
        observer.observe(featureTrigger);
      }
    } else {
      // 降级处理:立即加载
      this.loadFeature(featureName);
    }
  }
}

CSS 加载优化

css
/* 1. 关键 CSS 内联 */
<style>
/* 关键路径 CSS - 首屏渲染必需 */
.header { 
  height: 60px; 
  background: #fff; 
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.hero { 
  height: 400px; 
  display: flex; 
  align-items: center; 
  justify-content: center;
}
</style>

/* 2. 非关键 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>

/* 3. CSS 代码分割 */
/* 基础样式 */
@import url('./base.css');
/* 组件样式 - 按需加载 */
@import url('./components/button.css');
@import url('./components/form.css');
/* 页面特定样式 */
@import url('./pages/home.css');

图片优化

响应式图片

html
<!-- 1. 使用 srcset 和 sizes -->
<img 
  srcset="image-320w.jpg 320w, 
          image-640w.jpg 640w, 
          image-1280w.jpg 1280w"
  sizes="(max-width: 320px) 280px, 
         (max-width: 640px) 600px, 
         1200px"
  src="image-1280w.jpg" 
  alt="描述文字">

<!-- 2. 使用 picture 元素 -->
<picture>
  <source media="(min-width: 1200px)" srcset="large-image.webp" type="image/webp">
  <source media="(min-width: 1200px)" srcset="large-image.jpg">
  <source media="(min-width: 640px)" srcset="medium-image.webp" type="image/webp">
  <source media="(min-width: 640px)" srcset="medium-image.jpg">
  <img src="small-image.jpg" alt="响应式图片">
</picture>

<!-- 3. 懒加载图片 -->
<img 
  data-src="image.jpg" 
  data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
  class="lazy-image"
  alt="懒加载图片">

图片懒加载实现

javascript
class ImageLazyLoader {
  constructor() {
    this.imageObserver = null;
    this.init();
  }
  
  init() {
    if ('IntersectionObserver' in window) {
      this.imageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            this.loadImage(entry.target);
            observer.unobserve(entry.target);
          }
        });
      }, {
        rootMargin: '50px 0px', // 提前 50px 开始加载
        threshold: 0.01
      });
      
      // 观察所有懒加载图片
      document.querySelectorAll('img[data-src]').forEach(img => {
        this.imageObserver.observe(img);
      });
    } else {
      // 降级处理:立即加载所有图片
      document.querySelectorAll('img[data-src]').forEach(img => {
        this.loadImage(img);
      });
    }
  }
  
  loadImage(img) {
    // 设置加载中的占位符
    img.src = this.getPlaceholder(img);
    
    // 创建新图片对象预加载
    const imageLoader = new Image();
    imageLoader.onload = () => {
      // 替换为真实图片
      img.src = img.dataset.src;
      img.srcset = img.dataset.srcset || '';
      
      // 触发加载完成事件
      img.dispatchEvent(new CustomEvent('lazyloaded'));
    };
    
    imageLoader.onerror = () => {
      // 加载失败,显示错误占位符
      img.src = this.getErrorPlaceholder();
      img.dispatchEvent(new CustomEvent('lazyerror'));
    };
    
    imageLoader.src = img.dataset.src;
  }
  
  getPlaceholder(img) {
    // 根据图片尺寸生成小尺寸占位符或使用 SVG 占位符
    return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2VlZSIvPjwvc3ZnPg==';
  }
  
  getErrorPlaceholder() {
    return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2NjYyIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlIEVycm9yPC90ZXh0Pjwvc3ZnPg==';
  }
}

// 初始化图片懒加载
document.addEventListener('DOMContentLoaded', () => {
  new ImageLazyLoader();
});

缓存策略

HTTP 缓存

javascript
// 1. Service Worker 缓存策略
const CACHE_NAME = 'app-v1.2.3';
const STATIC_CACHE = [
  '/',
  '/css/app.css',
  '/js/app.js',
  '/images/logo.png'
];

// 安装 Service Worker
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(STATIC_CACHE))
      .then(() => self.skipWaiting())
  );
});

// 拦截请求并使用缓存
self.addEventListener('fetch', event => {
  const { request } = event;
  
  // 对于 HTML 页面,使用网络优先策略
  if (request.destination === 'document') {
    event.respondWith(networkFirst(request));
  } 
  // 对于静态资源,使用缓存优先策略
  else if (isStaticAsset(request)) {
    event.respondWith(cacheFirst(request));
  } 
  // 对于 API 请求,使用网络优先但降级到缓存
  else {
    event.respondWith(networkFirst(request));
  }
});

// 缓存优先策略
async function cacheFirst(request) {
  const cachedResponse = await caches.match(request);
  if (cachedResponse) {
    return cachedResponse;
  }
  
  const networkResponse = await fetch(request);
  if (networkResponse.ok) {
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, networkResponse.clone());
  }
  return networkResponse;
}

// 网络优先策略
async function networkFirst(request) {
  try {
    const networkResponse = await fetch(request);
    if (networkResponse.ok) {
      const cache = await caches.open(CACHE_NAME);
      cache.put(request, networkResponse.clone());
    }
    return networkResponse;
  } catch (error) {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    throw error;
  }
}

function isStaticAsset(request) {
  return request.destination === 'script' || 
         request.destination === 'style' || 
         request.destination === 'image';
}

浏览器缓存头配置

javascript
// Express.js 中的缓存头配置
const express = require('express');
const app = express();

// 静态资源缓存
app.use('/static', express.static('public', {
  maxAge: '1y', // 静态资源长期缓存
  etag: true,
  lastModified: true
}));

// 版本化静态资源
app.get('/assets/:version/*', (req, res, next) => {
  // 设置长期缓存
  res.set('Cache-Control', 'public, max-age=31536000'); // 1年
  express.static('public')(req, res, next);
});

// API 响应缓存
app.get('/api/data', (req, res) => {
  const data = getData();
  
  // 设置 ETag
  res.set('ETag', generateETag(data));
  
  // 检查 If-None-Match
  if (req.headers['if-none-match'] === generateETag(data)) {
    return res.status(304).end();
  }
  
  res.json(data);
});

// 生成 ETag
function generateETag(data) {
  const crypto = require('crypto');
  return crypto.createHash('md5').update(JSON.stringify(data)).digest('hex');
}

// 获取数据函数
function getData() {
  // 实际的数据获取逻辑
  return { timestamp: Date.now(), data: 'example' };
}

渲染性能优化

虚拟滚动

javascript
class VirtualScroll {
  constructor(container, itemHeight, totalItems, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.totalItems = totalItems;
    this.renderItem = renderItem;
    this.visibleStart = 0;
    this.visibleEnd = 0;
    
    this.init();
  }
  
  init() {
    this.container.style.height = `${this.totalItems * this.itemHeight}px`;
    this.container.style.position = 'relative';
    this.container.style.overflow = 'auto';
    
    this.itemsContainer = document.createElement('div');
    this.itemsContainer.style.position = 'absolute';
    this.itemsContainer.style.top = '0';
    this.itemsContainer.style.left = '0';
    this.itemsContainer.style.width = '100%';
    
    this.container.appendChild(this.itemsContainer);
    
    this.container.addEventListener('scroll', this.onScroll.bind(this));
    this.onScroll(); // 初始化
  }
  
  onScroll() {
    const scrollTop = this.container.scrollTop;
    const containerHeight = this.container.clientHeight;
    
    this.visibleStart = Math.floor(scrollTop / this.itemHeight);
    this.visibleEnd = Math.min(
      this.totalItems - 1,
      Math.ceil((scrollTop + containerHeight) / this.itemHeight)
    );
    
    // 计算需要渲染的项目数量
    const itemsToRender = this.visibleEnd - this.visibleStart + 1;
    
    // 清空现有项目
    this.itemsContainer.innerHTML = '';
    
    // 渲染可见项目
    for (let i = this.visibleStart; i <= this.visibleEnd; i++) {
      const item = this.renderItem(i);
      item.style.position = 'absolute';
      item.style.top = `${i * this.itemHeight}px`;
      item.style.left = '0';
      item.style.width = '100%';
      item.style.height = `${this.itemHeight}px`;
      
      this.itemsContainer.appendChild(item);
    }
  }
}

// 使用示例
const container = document.getElementById('virtual-scroll-container');
const virtualScroll = new VirtualScroll(
  container,
  50, // 每项高度
  10000, // 总项目数
  (index) => {
    // 渲染单个项目
    const div = document.createElement('div');
    div.textContent = `Item ${index}`;
    div.style.borderBottom = '1px solid #eee';
    div.style.padding = '10px';
    return div;
  }
);

防抖和节流

javascript
// 防抖函数
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// 节流函数
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 实际应用示例
class PerformanceOptimizedComponent {
  constructor() {
    // 防抖搜索
    this.debouncedSearch = debounce(this.performSearch.bind(this), 300);
    
    // 节流滚动事件
    this.throttledScroll = throttle(this.handleScroll.bind(this), 100);
    
    this.init();
  }
  
  init() {
    // 搜索框事件
    const searchInput = document.getElementById('search');
    searchInput.addEventListener('input', (e) => {
      this.debouncedSearch(e.target.value);
    });
    
    // 滚动事件
    window.addEventListener('scroll', this.throttledScroll);
  }
  
  performSearch(query) {
    if (query.length < 2) return;
    
    // 执行搜索逻辑
    console.log('Searching for:', query);
    // 实际的搜索实现
  }
  
  handleScroll() {
    // 处理滚动逻辑
    const scrollPosition = window.scrollY;
    console.log('Scroll position:', scrollPosition);
    // 实际的滚动处理逻辑
  }
}

Webpack 构建优化

代码分割配置

javascript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js',
  },
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
    publicPath: '/'
  },
  
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 分离第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        // 分离公共代码
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true
        },
        // 分离大的第三方库
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          chunks: 'all',
          priority: 20
        }
      }
    },
    
    // 运行时代码分离
    runtimeChunk: {
      name: 'runtime'
    }
  },
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      chunks: ['main', 'runtime', 'vendors']
    })
  ]
};

动态导入优化

javascript
// 路由级别的代码分割
const routes = [
  {
    path: '/',
    component: () => import('./pages/Home.js')
  },
  {
    path: '/about',
    component: () => import('./pages/About.js')
  },
  {
    path: '/dashboard',
    component: () => import('./pages/Dashboard.js')
  }
];

// 组件级别的代码分割
const LazyComponent = React.lazy(() => import('./ExpensiveComponent'));

function App() {
  return (
    <div>
      <React.Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </React.Suspense>
    </div>
  );
}

// 功能模块的懒加载
class FeatureLoader {
  async loadChartModule() {
    const { Chart } = await import('./modules/chart');
    return Chart;
  }
  
  async loadEditorModule() {
    const { Editor } = await import('./modules/editor');
    return Editor;
  }
  
  // 预加载功能模块
  preloadModules() {
    // 在空闲时间预加载
    if ('requestIdleCallback' in window) {
      requestIdleCallback(async () => {
        await Promise.allSettled([
          import('./modules/chart'),
          import('./modules/editor'),
          import('./modules/gallery')
        ]);
      });
    }
  }
}

性能监控

Web Vitals 监控

javascript
// Core Web Vitals 监控
class WebVitalsMonitor {
  constructor() {
    this.metrics = {
      lcp: null,
      fcp: null,
      cls: null,
      fid: null,
      ttfb: null
    };
    
    this.init();
  }
  
  init() {
    // Largest Contentful Paint (LCP)
    import('web-vitals').then(({ getLCP }) => {
      getLCP(metric => {
        this.metrics.lcp = metric;
        this.reportMetric('LCP', metric);
      });
    });
    
    // First Contentful Paint (FCP)
    import('web-vitals').then(({ getFCP }) => {
      getFCP(metric => {
        this.metrics.fcp = metric;
        this.reportMetric('FCP', metric);
      });
    });
    
    // Cumulative Layout Shift (CLS)
    import('web-vitals').then(({ getCLS }) => {
      getCLS(metric => {
        this.metrics.cls = metric;
        this.reportMetric('CLS', metric);
      });
    });
    
    // First Input Delay (FID)
    import('web-vitals').then(({ getFID }) => {
      getFID(metric => {
        this.metrics.fid = metric;
        this.reportMetric('FID', metric);
      });
    });
    
    // Time to First Byte (TTFB)
    import('web-vitals').then(({ getTTFB }) => {
      getTTFB(metric => {
        this.metrics.ttfb = metric;
        this.reportMetric('TTFB', metric);
      });
    });
  }
  
  reportMetric(name, metric) {
    // 发送指标到分析服务
    console.log(`${name}:`, metric.value);
    
    // 可以发送到服务器或分析工具
    this.sendToAnalytics({
      metric: name,
      value: metric.value,
      id: metric.id,
      navigationType: metric.navigationType
    });
  }
  
  sendToAnalytics(data) {
    // 使用 sendBeacon 确保数据发送
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/analytics', JSON.stringify(data));
    } else {
      // 降级处理
      fetch('/analytics', {
        method: 'POST',
        body: JSON.stringify(data),
        headers: { 'Content-Type': 'application/json' }
      }).catch(err => console.error('Analytics error:', err));
    }
  }
  
  getMetrics() {
    return this.metrics;
  }
}

// 初始化监控
new WebVitalsMonitor();

实际优化案例

电商网站优化案例

javascript
// 电商网站性能优化实现
class EcommercePerformanceOptimizer {
  constructor() {
    this.resourceLoader = new ResourceLoader();
    this.imageOptimizer = new ImageLazyLoader();
    this.cacheManager = new CacheManager();
    
    this.init();
  }
  
  init() {
    // 1. 首屏优化
    this.optimizeCriticalRenderingPath();
    
    // 2. 资源预加载
    this.preloadCriticalResources();
    
    // 3. 非关键功能延迟加载
    this.delayNonCriticalFeatures();
  }
  
  // 优化关键渲染路径
  optimizeCriticalRenderingPath() {
    // 内联关键 CSS
    const criticalCSS = `
      .header { height: 60px; background: #fff; }
      .hero { height: 400px; display: flex; }
      .product-card { display: block; }
    `;
    
    const style = document.createElement('style');
    style.textContent = criticalCSS;
    document.head.appendChild(style);
  }
  
  // 预加载关键资源
  preloadCriticalResources() {
    // 预加载首屏图片
    const heroImages = document.querySelectorAll('.hero-image');
    heroImages.forEach(img => {
      this.preloadImage(img.src);
    });
    
    // 预加载关键字体
    this.preloadFont('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
  }
  
  // 延迟非关键功能
  delayNonCriticalFeatures() {
    // 使用 Intersection Observer 延迟加载功能
    const featureSections = document.querySelectorAll('[data-lazy-feature]');
    
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const featureName = entry.target.dataset.lazyFeature;
            this.loadFeature(featureName);
            observer.unobserve(entry.target);
          }
        });
      });
      
      featureSections.forEach(section => observer.observe(section));
    }
  }
  
  async loadFeature(featureName) {
    switch(featureName) {
      case 'reviews':
        const { Reviews } = await import('./features/reviews.js');
        new Reviews();
        break;
      case 'recommendations':
        const { Recommendations } = await import('./features/recommendations.js');
        new Recommendations();
        break;
      case 'chat':
        const { ChatWidget } = await import('./features/chat.js');
        new ChatWidget();
        break;
    }
  }
  
  preloadImage(src) {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'image';
    link.href = src;
    document.head.appendChild(link);
  }
  
  preloadFont(href) {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'style';
    link.href = href;
    document.head.appendChild(link);
  }
}

// 启动优化器
document.addEventListener('DOMContentLoaded', () => {
  new EcommercePerformanceOptimizer();
});

性能优化检查清单

javascript
// 前端性能优化检查清单
const performanceChecklist = {
  // 加载性能
  loading: [
    '使用关键 CSS 内联首屏样式',
    '延迟非关键 CSS 加载',
    '使用 defer/async 加载 JavaScript',
    '实现资源预加载策略',
    '压缩和合并资源文件',
    '使用 CDN 加速静态资源',
    '启用 Gzip/Brotli 压缩'
  ],
  
  // 渲染性能
  rendering: [
    '避免强制同步布局',
    '使用 transform 和 opacity 实现动画',
    '实现虚拟滚动处理大量数据',
    '使用防抖和节流处理频繁事件',
    '优化 DOM 操作',
    '使用 DocumentFragment 批量更新'
  ],
  
  // 网络性能
  network: [
    '实现图片懒加载',
    '使用响应式图片',
    '压缩图片资源',
    '使用 WebP 格式',
    '实现 HTTP 缓存策略',
    '使用 Service Worker 离线缓存'
  ],
  
  // 构建优化
  build: [
    '实现代码分割',
    '使用 Tree Shaking 移除未使用代码',
    '压缩 JavaScript 和 CSS',
    '使用长期缓存策略',
    '实现资源版本控制',
    '使用构建时优化'
  ],
  
  // 监控
  monitoring: [
    '监控 Core Web Vitals',
    '实现性能指标收集',
    '设置性能预算',
    '定期进行性能审计',
    '监控第三方脚本影响',
    '实现错误和性能监控'
  ]
};

// 性能评分计算器
class PerformanceScoreCalculator {
  calculateScore(category, itemsCompleted) {
    const totalItems = performanceChecklist[category].length;
    return Math.round((itemsCompleted / totalItems) * 100);
  }
  
  generateReport() {
    const categories = Object.keys(performanceChecklist);
    const report = {};
    
    categories.forEach(category => {
      report[category] = this.calculateScore(category, 0); // 需要实际的完成数量
    });
    
    return report;
  }
}

总结

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

  1. 资源加载优化 - 合理安排资源加载顺序和策略
  2. 渲染性能优化 - 优化 DOM 操作和动画性能
  3. 缓存策略 - 实现多层缓存机制
  4. 构建优化 - 优化打包和部署流程
  5. 监控分析 - 持续监控和优化性能指标

通过系统性的性能优化,可以显著提升用户体验和网站性能。