Skip to content
On this page

资源优化

资源优化是提升 Web 应用性能的关键环节,涉及对静态资源(如 JavaScript、CSS、图片等)的优化处理。本指南将详细介绍各种资源优化技术和最佳实践。

JavaScript 优化

代码分割和懒加载

javascript
// Webpack 配置实现代码分割
// webpack.config.js
module.exports = {
  mode: 'production',
  entry: {
    main: './src/main.js',
    vendor: './src/vendor.js'
  },
  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'
    }
  }
};

// 动态导入实现懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const Dashboard = React.lazy(() => import('./Dashboard'));

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

// 路由级别的懒加载
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function AppRouter() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

JavaScript 代码优化

javascript
// 1. 避免全局变量污染
(function(global) {
  'use strict';
  
  // 私有变量和函数
  const privateVar = 'private';
  
  function privateFunction() {
    // 私有逻辑
  }
  
  // 公共接口
  global.MyApp = {
    init: function() {
      // 初始化逻辑
    },
    publicMethod: function() {
      // 公共方法
    }
  };
}(window));

// 2. 使用现代 JavaScript 特性优化
class OptimizedComponent {
  constructor(options = {}) {
    // 使用解构和默认值
    this.config = {
      timeout: 5000,
      retries: 3,
      ...options
    };
    
    // 绑定方法以避免在事件处理器中重复绑定
    this.handleClick = this.handleClick.bind(this);
    this.handleResize = this.debounce(this.handleResize.bind(this), 250);
  }
  
  handleClick(event) {
    // 优化的事件处理
    event.preventDefault();
    // 避免多次点击
    if (this.isProcessing) return;
    
    this.isProcessing = true;
    // 执行操作
    this.performAction()
      .finally(() => {
        this.isProcessing = false;
      });
  }
  
  // 防抖函数
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
  
  // 虚拟滚动优化大量列表项
  virtualScroll(items, containerHeight, itemHeight) {
    const startIndex = Math.floor(this.scrollOffset / itemHeight);
    const visibleCount = Math.ceil(containerHeight / itemHeight);
    const endIndex = Math.min(startIndex + visibleCount + 5, items.length); // 额外加载5个作为缓冲
    
    return items.slice(startIndex, endIndex).map((item, index) => ({
      ...item,
      style: { transform: `translateY(${(startIndex + index) * itemHeight}px)` }
    }));
  }
}

// 3. 内存优化
class MemoryOptimizedClass {
  constructor() {
    this.cache = new Map();
    this.eventListeners = new Set();
  }
  
  // 及时清理资源
  destroy() {
    // 清理缓存
    this.cache.clear();
    
    // 移除事件监听器
    this.eventListeners.forEach(listener => {
      listener.target.removeEventListener(listener.type, listener.handler);
    });
    this.eventListeners.clear();
    
    // 断开引用
    this.cache = null;
    this.eventListeners = null;
  }
  
  // 使用 WeakMap 避免内存泄漏
  static elementData = new WeakMap();
  
  storeElementData(element, data) {
    MemoryOptimizedClass.elementData.set(element, data);
  }
  
  getElementData(element) {
    return MemoryOptimizedClass.elementData.get(element);
  }
}

CSS 优化

CSS 代码分割和优化

css
/* 1. 关键 CSS 内联优化 */
/* 关键路径 CSS - 内联到 HTML 中 */
.critical {
  display: block;
  margin: 0 auto;
  font-family: Arial, sans-serif;
}

/* 2. 非关键 CSS 异步加载 */
/* 使用 JavaScript 异步加载非关键 CSS */
/* 在页面加载后再加载 */
javascript
// 异步加载 CSS
function loadCSS(href) {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = href;
  link.media = 'print'; // 初始设置为打印媒体类型,避免阻塞渲染
  link.onload = () => link.media = 'all'; // 加载完成后切换到所有媒体类型
  
  document.head.appendChild(link);
}

// 预加载关键 CSS
function preloadCriticalCSS(href) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = href;
  document.head.appendChild(link);
}

// 使用示例
preloadCriticalCSS('/css/critical.css');
loadCSS('/css/non-critical.css');

CSS 选择器优化

css
/* 好的选择器 - 避免过度嵌套 */
.header { }
.header-nav { }
.header-nav-item { }

/* 避免低效的选择器 */
/* 
div > ul > li > a  /* 过度限定 */
* { margin: 0; }    /* 通配符选择器 */
ul li a { }         /* 过多遍历 */
*/

/* 使用 CSS 自定义属性优化主题切换 */
:root {
  --primary-color: #007bff;
  --secondary-color: #6c757d;
  --font-size-base: 16px;
  --transition-fast: 0.15s ease;
  --transition-normal: 0.3s ease;
}

.button {
  background-color: var(--primary-color);
  transition: all var(--transition-normal);
}

.button:hover {
  background-color: color-mix(in srgb, var(--primary-color) 80%, black);
}

图片优化

响应式图片实现

html
<!-- 1. 使用 srcset 实现响应式图片 -->
<img 
  src="/images/default.jpg" 
  srcset="/images/small.jpg 480w, 
          /images/medium.jpg 800w, 
          /images/large.jpg 1200w"
  sizes="(max-width: 480px) 100vw, 
         (max-width: 800px) 50vw, 
         25vw"
  alt="响应式图片示例"
  loading="lazy"
>

<!-- 2. 使用 picture 元素提供多种格式 -->
<picture>
  <source srcset="/images/image.webp" type="image/webp">
  <source srcset="/images/image.avif" type="image/avif">
  <img src="/images/image.jpg" alt="多种格式图片">
</picture>

<!-- 3. 使用 CSS 实现背景图片优化 -->
<div class="hero-image" style="background-image: url('/images/hero.webp')"></div>
css
/* 响应式背景图片 */
.hero-image {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  aspect-ratio: 16/9; /* 现代浏览器支持 */
}

/* 使用 CSS 自定义属性实现图片懒加载占位符 */
.image-container {
  background-color: #f0f0f0;
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23e0e0e0"/><path d="M20,20 L80,80 M80,20 L20,80" stroke="%23cccccc" stroke-width="2"/></svg>');
  background-size: 50px 50px;
  transition: opacity 0.3s ease;
}

.image-container.loaded {
  opacity: 1;
}

图片懒加载实现

javascript
// Intersection Observer 实现图片懒加载
class ImageLazyLoader {
  constructor(options = {}) {
    this.options = {
      rootMargin: options.rootMargin || '50px 0px',
      threshold: options.threshold || 0.01,
      ...options
    };
    
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.options
    );
    
    this.loadImages();
  }
  
  loadImages() {
    const images = document.querySelectorAll('img[data-src]');
    images.forEach(img => this.observer.observe(img));
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        this.loadImage(img);
        this.observer.unobserve(img);
      }
    });
  }
  
  loadImage(img) {
    return new Promise((resolve, reject) => {
      const imageLoader = new Image();
      
      imageLoader.onload = () => {
        img.src = img.dataset.src;
        img.classList.add('loaded');
        resolve(img);
      };
      
      imageLoader.onerror = () => {
        img.src = img.dataset.error || '/images/placeholder.png';
        reject(new Error(`Failed to load image: ${img.dataset.src}`));
      };
      
      imageLoader.src = img.dataset.src;
      
      // 清理 data-src 属性
      img.removeAttribute('data-src');
    });
  }
  
  destroy() {
    this.observer.disconnect();
  }
}

// 使用懒加载
document.addEventListener('DOMContentLoaded', () => {
  new ImageLazyLoader({
    rootMargin: '100px 0px',
    threshold: 0.1
  });
});

Webpack 资源优化

高级 Webpack 配置

javascript
// webpack.production.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
  mode: 'production',
  entry: {
    app: './src/index.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    clean: true // 清理输出目录
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除 console
            drop_debugger: true, // 移除 debugger
            pure_funcs: ['console.log'] // 移除特定函数调用
          },
          mangle: {
            reserved: ['$', 'jQuery'] // 保留特定名称
          }
        }
      }),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: -10,
          chunks: 'all'
        }
      }
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime-${entrypoint.name}`
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                useBuiltIns: 'usage',
                corejs: 3,
                targets: '> 0.25%, not dead'
              }]
            ],
            plugins: [
              '@babel/plugin-transform-runtime' // 避免污染全局作用域
            ]
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024 // 8KB 以下转为 base64
          }
        },
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[name].[contenthash:8].chunk.css'
    }),
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};

资源预加载和预获取

资源提示优化

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

<!-- 2. 预获取非关键资源 -->
<link rel="prefetch" href="/js/next-page.js">
<link rel="prefetch" href="/images/next-page-image.jpg">

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

<!-- 4. 预连接 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
javascript
// 动态资源预加载
class ResourcePreloader {
  static preloadResource(href, as) {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.as = as;
      link.href = href;
      
      link.onload = resolve;
      link.onerror = reject;
      
      document.head.appendChild(link);
    });
  }
  
  static async preloadCriticalResources() {
    const resources = [
      { href: '/css/main.css', as: 'style' },
      { href: '/js/app.js', as: 'script' },
      { href: '/fonts/main.woff2', as: 'font', type: 'font/woff2', crossorigin: true }
    ];
    
    await Promise.all(
      resources.map(resource => {
        const link = document.createElement('link');
        link.rel = 'preload';
        link.as = resource.as;
        link.href = resource.href;
        
        if (resource.type) link.type = resource.type;
        if (resource.crossorigin) link.crossOrigin = resource.crossorigin;
        
        document.head.appendChild(link);
      })
    );
  }
  
  // 预获取可能需要的资源
  static prefetchResource(href) {
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = href;
    document.head.appendChild(link);
  }
}

// 页面加载完成后预获取后续页面资源
window.addEventListener('load', () => {
  ResourcePreloader.preloadCriticalResources();
  
  // 预获取用户可能访问的页面资源
  setTimeout(() => {
    ResourcePreloader.prefetchResource('/js/dashboard.js');
    ResourcePreloader.prefetchResource('/js/profile.js');
  }, 2000);
});

资源缓存策略

智能缓存实现

javascript
// 资源缓存管理器
class ResourceManager {
  constructor() {
    this.cache = new Map();
    this.maxCacheSize = 100;
  }
  
  // 缓存静态资源
  async loadResource(url, options = {}) {
    // 检查缓存
    if (this.cache.has(url)) {
      const cached = this.cache.get(url);
      if (Date.now() - cached.timestamp < (options.maxAge || 300000)) { // 5分钟
        return cached.data;
      }
    }
    
    // 加载资源
    const response = await fetch(url);
    const data = await response.text();
    
    // 存入缓存
    this.setCache(url, data, options.maxAge);
    
    return data;
  }
  
  setCache(url, data, maxAge = 300000) {
    if (this.cache.size >= this.maxCacheSize) {
      // 简单的 FIFO 清理
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    
    this.cache.set(url, {
      data,
      timestamp: Date.now(),
      maxAge
    });
  }
  
  // 预加载资源到缓存
  async preloadResources(urls) {
    const promises = urls.map(url => 
      this.loadResource(url).catch(err => {
        console.warn(`Failed to preload ${url}:`, err);
      })
    );
    
    await Promise.all(promises);
  }
}

// 使用资源管理器
const resourceManager = new ResourceManager();

// 预加载关键资源
resourceManager.preloadResources([
  '/api/config',
  '/css/theme.css',
  '/js/utils.js'
]);

资源优化监控

性能监控

javascript
// 资源加载性能监控
class ResourcePerformanceMonitor {
  constructor() {
    this.metrics = {
      resourceLoadTimes: [],
      cacheHitRate: 0,
      totalSize: 0,
      compressedSize: 0
    };
  }
  
  // 监控资源加载
  monitorResourceLoad(resourceUrl) {
    const startMark = `resource-start-${resourceUrl}`;
    const endMark = `resource-end-${resourceUrl}`;
    
    performance.mark(startMark);
    
    return (loadTime, size) => {
      performance.mark(endMark);
      performance.measure(`resource-${resourceUrl}`, startMark, endMark);
      
      this.metrics.resourceLoadTimes.push({
        url: resourceUrl,
        loadTime,
        size,
        timestamp: Date.now()
      });
      
      this.metrics.totalSize += size;
    };
  }
  
  // 获取性能指标
  getMetrics() {
    const entries = performance.getEntriesByType('measure');
    const loadTimes = entries
      .filter(entry => entry.name.startsWith('resource-'))
      .map(entry => entry.duration);
    
    const avgLoadTime = loadTimes.reduce((a, b) => a + b, 0) / loadTimes.length;
    
    return {
      ...this.metrics,
      averageLoadTime: avgLoadTime,
      resourceCount: entries.length
    };
  }
  
  // 资源优化建议
  getOptimizationSuggestions() {
    const metrics = this.getMetrics();
    const suggestions = [];
    
    if (metrics.averageLoadTime > 1000) {
      suggestions.push('资源加载时间过长,考虑压缩或 CDN');
    }
    
    if (metrics.totalSize > 1024 * 1024) { // 1MB
      suggestions.push('资源总大小过大,考虑代码分割');
    }
    
    return suggestions;
  }
}

const perfMonitor = new ResourcePerformanceMonitor();

// 监控脚本加载
function loadScriptWithMonitoring(src) {
  const monitor = perfMonitor.monitorResourceLoad(src);
  const startTime = Date.now();
  
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = () => {
      const loadTime = Date.now() - startTime;
      monitor(loadTime, 0); // 简化处理,实际应该获取文件大小
      resolve(script);
    };
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

资源优化检查清单

  • [ ] 实现 JavaScript 代码分割
  • [ ] 使用动态导入实现懒加载
  • [ ] 优化 CSS 选择器性能
  • [ ] 实现响应式图片
  • [ ] 使用 WebP/AVIF 等现代图片格式
  • [ ] 实现图片懒加载
  • [ ] 配置资源预加载和预获取
  • [ ] 使用 CDN 分发静态资源
  • [ ] 实现适当的缓存策略
  • [ ] 压缩和最小化资源文件
  • [ ] 移除未使用的代码(Tree Shaking)
  • [ ] 使用资源提示优化加载顺序
  • [ ] 监控和分析资源加载性能
  • [ ] 实施错误处理和降级策略

通过实施这些资源优化策略,您可以显著提升 Web 应用的加载速度和用户体验。