前端资源优化之【资源压缩和请求优化】

85 阅读8分钟

前端资源优化

相关资料

相关问题

请说说你做过的关于资源方面的优化?

  • 资源压缩:使用工具如 uglify-js、CSSNano 对 JavaScript 和 CSS文件进行压缩,以减少文件体积。图片则采用 imagemin 进行压缩优化。

  • 文件合并:通过 Webpack 等构建工具将多个 CsS和JS文件合并,减少 HTTP 请求数量。

  • 资源懒加载与异步加载:对于非关键资源(如图片和次要 JavaScript 文件),我采用了懒加载策略,只有在需要时才加载这些资源。同时,关键的JS 脚本采用异步加载(如 async 或 defer),以提高页面的首屏渲染速度。

  • 字体优化:使用字体子集化工具(如 Fontmin)减少字体文件的体积,并使用 woff2 格式的字体文件以获得更好的压缩效果。

  • 缓存策略:配置 Cache-Control、ETag 和 Last-Modified 等 HTTP 头来优化浏览器缓存,并使用 Service Worker 进行更细粒度的缓存控制。对于长时间不变的资源,设置了较长的缓存时间。

  • 预加载与预请求:使用 link 标签的 rel="preload" 和 rel="prefetch"属性来预加载和预请求关键资源,从而提升页面加载速度。

  • CDN:将静态资源部署到 CDN(内容分发网络)上,利用其多节点分布的优势,降低请求延迟。

前端如果想本地化存储数据,有什么方案?

  • LocalStorage:一种同步 API,用于存储少量的键值对数据(通常不超过 5MB)。数据没有过期时间,除非手动删除,否则会一直存在。

  • Sessionstorage:与 Localstorage类似,但数据仅在页面会话期间存储,即浏览器窗口关闭后数据会被清除。

  • IndexedDB:一种低级 API,提供了更大的存储空间和更强的查询能力,适用于需要存储结构化数据或大量数据的场景。IndexedDB 是异步的,性能较好,适合复杂的存储需求。

  • Cookies:用于在客户端存储少量数据,并且可以在客户端与服务器之间传递。通常用于会话管理,但由于大小限制(约 4KB)和安全问题,不推荐大量使用。

  • Service Worker Cache:通过 Service Worker,可以在浏览器缓存中存储网络请求的结果,便于离线访问和提升页面加载性能。适用于 PWA(渐进式 Web 应用)场景。

资源压缩和请求优化

资源压缩

JavaScript 和 CSS 文件压缩

使用 Terser 压缩 JavaScript 文件

Terser 是目前最流行的 JavaScript 压缩工具,能够有效减少文件体积并提升加载性能。

安装和基本配置:

// 安装 Terser
// npm install terser --save-dev

const { minify } = require('terser');
const fs = require('fs');

// 基础压缩配置
const terserOptions = {
  compress: {
    // 删除 console.log
    drop_console: true,
    // 删除 debugger
    drop_debugger: true,
    // 删除未使用的变量
    unused: true,
    // 优化 if 语句
    conditionals: true,
    // 移除死代码
    dead_code: true
  },
  mangle: {
    // 混淆变量名
    toplevel: true,
    // 保留特定名称
    reserved: ['$', 'jQuery']
  },
  format: {
    // 移除注释
    comments: false
  }
};

// 压缩单个文件
async function compressJavaScript(inputPath, outputPath) {
  try {
    const code = fs.readFileSync(inputPath, 'utf8');
    const result = await minify(code, terserOptions);
    
    fs.writeFileSync(outputPath, result.code);
    
    const originalSize = Buffer.byteLength(code, 'utf8');
    const compressedSize = Buffer.byteLength(result.code, 'utf8');
    const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
    
    console.log(`压缩完成: ${inputPath}`);
    console.log(`原始大小: ${originalSize} bytes`);
    console.log(`压缩后大小: ${compressedSize} bytes`);
    console.log(`压缩率: ${ratio}%`);
  } catch (error) {
    console.error('压缩失败:', error);
  }
}

// Webpack 集成配置
const TerserWebpackPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({
        terserOptions: {
          compress: {
            drop_console: process.env.NODE_ENV === 'production',
            drop_debugger: true,
            pure_funcs: ['console.log', 'console.info']
          },
          mangle: {
            safari10: true
          }
        },
        extractComments: false
      })
    ]
  }
};
使用 cssnano 压缩 CSS 文件

cssnano 是一个模块化的 CSS 压缩工具,通过多种优化策略来减少 CSS 文件体积。

// 安装 cssnano
// npm install cssnano postcss --save-dev

const postcss = require('postcss');
const cssnano = require('cssnano');
const fs = require('fs');

// cssnano 配置选项
const cssnanoOptions = {
  preset: ['default', {
    // 丢弃注释
    discardComments: {
      removeAll: true
    },
    // 规范化空白
    normalizeWhitespace: true,
    // 合并规则
    mergeRules: true,
    // 压缩颜色值
    colormin: true,
    // 优化字体权重
    normalizeUnicode: true,
    // 删除未使用的 at-rules
    discardUnused: true
  }]
};

// 压缩 CSS 文件
async function compressCss(inputPath, outputPath) {
  try {
    const css = fs.readFileSync(inputPath, 'utf8');
    
    const result = await postcss([
      cssnano(cssnanoOptions)
    ]).process(css, { from: inputPath, to: outputPath });
    
    fs.writeFileSync(outputPath, result.css);
    
    if (result.map) {
      fs.writeFileSync(outputPath + '.map', result.map.toString());
    }
    
    const originalSize = Buffer.byteLength(css, 'utf8');
    const compressedSize = Buffer.byteLength(result.css, 'utf8');
    const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
    
    console.log(`CSS 压缩完成: ${inputPath}`);
    console.log(`压缩率: ${ratio}%`);
  } catch (error) {
    console.error('CSS 压缩失败:', error);
  }
}

// PostCSS 配置文件 (postcss.config.js)
module.exports = {
  plugins: [
    require('autoprefixer'),
    ...(process.env.NODE_ENV === 'production' ? [
      require('cssnano')({
        preset: ['default', {
          discardComments: { removeAll: true },
          normalizeWhitespace: true
        }]
      })
    ] : [])
  ]
};

图片压缩

使用 cwebp 将图片转换为 WebP 格式

WebP 格式能够在保持图片质量的同时显著减少文件大小,相比 JPEG 可节省 25-50% 的体积。

// 安装 imagemin 和相关插件
// npm install imagemin imagemin-webp imagemin-mozjpeg imagemin-pngquant --save-dev

const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const fs = require('fs');
const path = require('path');

// 图片压缩配置
const imageCompressionConfig = {
  webp: {
    quality: 80,
    method: 6,
    autoFilter: true
  },
  jpeg: {
    quality: 85,
    progressive: true
  },
  png: {
    quality: [0.6, 0.8],
    speed: 1
  }
};

// 批量压缩图片并转换为 WebP
async function compressImages(inputDir, outputDir) {
  try {
    // 压缩并转换为 WebP
    const webpFiles = await imagemin([`${inputDir}/*.{jpg,jpeg,png}`], {
      destination: `${outputDir}/webp`,
      plugins: [
        imageminWebp(imageCompressionConfig.webp)
      ]
    });

    // 压缩原格式图片作为回退
    const fallbackFiles = await imagemin([`${inputDir}/*.{jpg,jpeg,png}`], {
      destination: `${outputDir}/fallback`,
      plugins: [
        imageminMozjpeg(imageCompressionConfig.jpeg),
        imageminPngquant(imageCompressionConfig.png)
      ]
    });

    console.log('图片压缩完成:');
    console.log(`WebP 文件: ${webpFiles.length} 个`);
    console.log(`回退文件: ${fallbackFiles.length} 个`);

    return { webpFiles, fallbackFiles };
  } catch (error) {
    console.error('图片压缩失败:', error);
  }
}

// 生成响应式图片 HTML
function generateResponsiveImageHtml(imageName, alt = '') {
  const baseName = path.parse(imageName).name;
  
  return `
<picture>
  <source srcset="/images/webp/${baseName}.webp" type="image/webp">
  <source srcset="/images/fallback/${imageName}" type="image/jpeg">
  <img src="/images/fallback/${imageName}" alt="${alt}" loading="lazy">
</picture>`;
}

// 实用的图片优化工具函数
class ImageOptimizer {
  constructor(options = {}) {
    this.quality = options.quality || 80;
    this.outputDir = options.outputDir || './dist/images';
  }

  async processDirectory(inputDir) {
    const files = fs.readdirSync(inputDir);
    const imageFiles = files.filter(file => 
      /\.(jpg|jpeg|png|gif)$/i.test(file)
    );

    const results = await Promise.all(
      imageFiles.map(file => this.processImage(path.join(inputDir, file)))
    );

    return results;
  }

  async processImage(inputPath) {
    const startTime = Date.now();
    const originalStats = fs.statSync(inputPath);
    
    const result = await imagemin([inputPath], {
      destination: this.outputDir,
      plugins: [
        imageminWebp({ quality: this.quality }),
        imageminMozjpeg({ quality: this.quality }),
        imageminPngquant({ quality: [0.6, 0.8] })
      ]
    });

    const processTime = Date.now() - startTime;
    const compressionRatio = (
      (originalStats.size - result[0].data.length) / originalStats.size * 100
    ).toFixed(2);

    return {
      file: path.basename(inputPath),
      originalSize: originalStats.size,
      compressedSize: result[0].data.length,
      compressionRatio,
      processTime
    };
  }
}

文本文件压缩

Node API 来处理 gzip、brotli 压缩
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);

// 压缩选项配置
const compressionOptions = {
  gzip: {
    level: 9, // 最高压缩级别
    windowBits: 15,
    memLevel: 8
  },
  brotli: {
    params: {
      [zlib.constants.BROTLI_PARAM_QUALITY]: 11, // 最高质量
      [zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0
    }
  }
};

// 文件压缩类
class FileCompressor {
  constructor(options = {}) {
    this.compressionTypes = options.types || ['gzip', 'brotli'];
    this.threshold = options.threshold || 1024; // 只压缩大于1KB的文件
  }

  async compressFile(inputPath, outputDir = null) {
    const stats = fs.statSync(inputPath);
    
    if (stats.size < this.threshold) {
      console.log(`文件 ${inputPath} 小于阈值,跳过压缩`);
      return null;
    }

    const content = fs.readFileSync(inputPath);
    const results = {};

    // 并行压缩
    const compressionPromises = this.compressionTypes.map(async (type) => {
      try {
        let compressed;
        let extension;

        switch (type) {
          case 'gzip':
            compressed = await gzip(content, compressionOptions.gzip);
            extension = '.gz';
            break;
          case 'brotli':
            compressed = await brotliCompress(content, compressionOptions.brotli);
            extension = '.br';
            break;
          default:
            throw new Error(`不支持的压缩类型: ${type}`);
        }

        const outputPath = outputDir 
          ? path.join(outputDir, path.basename(inputPath) + extension)
          : inputPath + extension;

        fs.writeFileSync(outputPath, compressed);

        const compressionRatio = (
          (content.length - compressed.length) / content.length * 100
        ).toFixed(2);

        results[type] = {
          originalSize: content.length,
          compressedSize: compressed.length,
          compressionRatio,
          outputPath
        };

        console.log(`${type.toUpperCase()} 压缩完成: ${compressionRatio}% (${outputPath})`);
      } catch (error) {
        console.error(`${type} 压缩失败:`, error);
      }
    });

    await Promise.all(compressionPromises);
    return results;
  }

  async compressDirectory(inputDir, outputDir, filePattern = /\.(js|css|html|json|svg)$/) {
    const files = fs.readdirSync(inputDir);
    const targetFiles = files.filter(file => filePattern.test(file));

    const results = await Promise.all(
      targetFiles.map(file => 
        this.compressFile(path.join(inputDir, file), outputDir)
      )
    );

    return results.filter(result => result !== null);
  }

  // 中间件形式的实时压缩
  middleware() {
    return (req, res, next) => {
      const acceptEncoding = req.headers['accept-encoding'] || '';
      
      // 检查客户端支持的压缩格式
      const supportsBrotli = /\bbr\b/.test(acceptEncoding);
      const supportsGzip = /\bgzip\b/.test(acceptEncoding);
      
      if (!supportsBrotli && !supportsGzip) {
        return next();
      }

      const originalSend = res.send;
      
      res.send = function(data) {
        if (typeof data === 'string' && data.length > 1024) {
          const compressionType = supportsBrotli ? 'brotli' : 'gzip';
          
          if (compressionType === 'brotli') {
            zlib.brotliCompress(data, compressionOptions.brotli, (err, compressed) => {
              if (!err) {
                res.set('Content-Encoding', 'br');
                res.set('Content-Length', compressed.length);
                originalSend.call(this, compressed);
              } else {
                originalSend.call(this, data);
              }
            });
          } else {
            zlib.gzip(data, compressionOptions.gzip, (err, compressed) => {
              if (!err) {
                res.set('Content-Encoding', 'gzip');
                res.set('Content-Length', compressed.length);
                originalSend.call(this, compressed);
              } else {
                originalSend.call(this, data);
              }
            });
          }
        } else {
          originalSend.call(this, data);
        }
      };
      
      next();
    };
  }
}

// 使用示例
const compressor = new FileCompressor({
  types: ['gzip', 'brotli'],
  threshold: 1024
});

// 压缩构建输出
compressor.compressDirectory('./dist', './dist/compressed');
配置 Nginx 以使用 Gzip 和 Brotli 压缩
// Nginx 配置生成器
class NginxCompressionConfig {
  static generateGzipConfig(options = {}) {
    const config = {
      enabled: options.enabled !== false,
      level: options.level || 6,
      minLength: options.minLength || 1000,
      types: options.types || [
        'text/plain',
        'text/css',
        'text/javascript',
        'text/xml',
        'application/json',
        'application/javascript',
        'application/xml+rss',
        'application/atom+xml',
        'image/svg+xml'
      ]
    };

    return `
# Gzip 压缩配置
gzip ${config.enabled ? 'on' : 'off'};
gzip_vary on;
gzip_min_length ${config.minLength};
gzip_proxied any;
gzip_comp_level ${config.level};
gzip_types
    ${config.types.join('\n    ')};
`;
  }

  static generateBrotliConfig(options = {}) {
    const config = {
      enabled: options.enabled !== false,
      level: options.level || 6,
      minLength: options.minLength || 1000,
      types: options.types || [
        'text/plain',
        'text/css',
        'text/javascript',
        'application/json',
        'application/javascript',
        'application/xml+rss',
        'application/atom+xml',
        'image/svg+xml'
      ]
    };

    return `
# Brotli 压缩配置 (需要 ngx_brotli 模块)
brotli ${config.enabled ? 'on' : 'off'};
brotli_comp_level ${config.level};
brotli_min_length ${config.minLength};
brotli_types
    ${config.types.join('\n    ')};
`;
  }

  static generateCompleteConfig(options = {}) {
    return `
server {
    listen 80;
    server_name ${options.serverName || 'example.com'};
    root ${options.root || '/var/www/html'};
    index index.html index.htm;

    ${this.generateGzipConfig(options.gzip)}
    
    ${this.generateBrotliConfig(options.brotli)}

    # 缓存配置
    location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        
        # 预压缩文件支持
        location ~ \\.js$ {
            gzip_static on;
            brotli_static on;
        }
        
        location ~ \\.css$ {
            gzip_static on;
            brotli_static on;
        }
    }

    # HTML 文件缓存
    location ~* \\.html$ {
        expires 1h;
        add_header Cache-Control "public, must-revalidate";
    }
}`;
  }
}

// 生成 Nginx 配置
const nginxConfig = NginxCompressionConfig.generateCompleteConfig({
  serverName: 'myapp.com',
  root: '/var/www/myapp',
  gzip: {
    level: 9,
    minLength: 1000
  },
  brotli: {
    level: 11,
    minLength: 1000
  }
});

console.log(nginxConfig);

请求优化

减少 HTTP 请求数

每次 HTTP 请求都会消耗时间,因此减少HTTP 请求数可以显著提升页面性能。常见的方法包括

  • 合并文件:将多个 CSS或 JavaScript 文件合并为一个文件,减少请求次数。

  • 内联关键CSS 和 JavaScript:将关键的 CSS 和 Javascript 内联到 HTML 文件中,减少外部请求

文件合并策略实现:

// 资源合并工具
class AssetBundler {
  constructor(options = {}) {
    this.outputDir = options.outputDir || './dist';
    this.bundles = new Map();
  }

  // 创建 CSS 合并包
  createCssBundle(name, files) {
    const bundleContent = files.map(file => {
      const content = fs.readFileSync(file, 'utf8');
      return `/* ${path.basename(file)} */\n${content}\n`;
    }).join('\n');

    const outputPath = path.join(this.outputDir, `${name}.bundle.css`);
    fs.writeFileSync(outputPath, bundleContent);
    
    this.bundles.set(name, {
      type: 'css',
      files: files.length,
      size: Buffer.byteLength(bundleContent, 'utf8'),
      path: outputPath
    });

    return outputPath;
  }

  // 创建 JavaScript 合并包
  createJsBundle(name, files) {
    const bundleContent = files.map(file => {
      const content = fs.readFileSync(file, 'utf8');
      return `/* ${path.basename(file)} */\n(function() {\n${content}\n})();\n`;
    }).join('\n');

    const outputPath = path.join(this.outputDir, `${name}.bundle.js`);
    fs.writeFileSync(outputPath, bundleContent);
    
    this.bundles.set(name, {
      type: 'js',
      files: files.length,
      size: Buffer.byteLength(bundleContent, 'utf8'),
      path: outputPath
    });

    return outputPath;
  }

  // 生成资源清单
  generateManifest() {
    const manifest = {};
    
    for (const [name, bundle] of this.bundles) {
      manifest[name] = {
        type: bundle.type,
        files: bundle.files,
        size: bundle.size,
        url: `/bundles/${path.basename(bundle.path)}`
      };
    }

    const manifestPath = path.join(this.outputDir, 'bundle-manifest.json');
    fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
    
    return manifest;
  }
}

// 智能资源加载器
class ResourceLoader {
  constructor() {
    this.loadedResources = new Set();
    this.loadingPromises = new Map();
  }

  // 动态加载 CSS
  loadCSS(url) {
    if (this.loadedResources.has(url)) {
      return Promise.resolve();
    }

    if (this.loadingPromises.has(url)) {
      return this.loadingPromises.get(url);
    }

    const promise = new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = url;
      
      link.onload = () => {
        this.loadedResources.add(url);
        resolve();
      };
      
      link.onerror = () => {
        this.loadingPromises.delete(url);
        reject(new Error(`Failed to load CSS: ${url}`));
      };
      
      document.head.appendChild(link);
    });

    this.loadingPromises.set(url, promise);
    return promise;
  }

  // 动态加载 JavaScript
  loadJS(url) {
    if (this.loadedResources.has(url)) {
      return Promise.resolve();
    }

    if (this.loadingPromises.has(url)) {
      return this.loadingPromises.get(url);
    }

    const promise = new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.async = true;
      
      script.onload = () => {
        this.loadedResources.add(url);
        resolve();
      };
      
      script.onerror = () => {
        this.loadingPromises.delete(url);
        reject(new Error(`Failed to load JS: ${url}`));
      };
      
      document.head.appendChild(script);
    });

    this.loadingPromises.set(url, promise);
    return promise;
  }

  // 批量加载资源
  async loadBundle(resources) {
    const loadPromises = resources.map(resource => {
      if (resource.type === 'css') {
        return this.loadCSS(resource.url);
      } else if (resource.type === 'js') {
        return this.loadJS(resource.url);
      }
    });

    try {
      await Promise.all(loadPromises);
      console.log('资源包加载完成');
    } catch (error) {
      console.error('资源包加载失败:', error);
      throw error;
    }
  }
}

使用懒加载、预加载、预请求

优化资源加载顺序,可以减少首次渲染时间和整体加载时间。

  • 懒加载:对于图片、视频等资源,只有当它们进入视口时才加载,以减少初始页面加载时间。
  • 预加载:提前加载即将使用的资源,例如字体、关键 CSS、JavaScript 等。
  • 预请求:提前进行 DNS 解析或连接建立,以减少后续请求的延迟。
// 图片懒加载实现
class LazyImageLoader {
  constructor(options = {}) {
    this.threshold = options.threshold || '50px';
    this.observerOptions = {
      root: null,
      rootMargin: this.threshold,
      threshold: 0.1
    };
    
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.observerOptions
    );
    
    this.init();
  }

  init() {
    const lazyImages = document.querySelectorAll('img[data-src]');
    lazyImages.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) {
    const src = img.getAttribute('data-src');
    const srcset = img.getAttribute('data-srcset');
    
    // 创建新的图片对象进行预加载
    const imageLoader = new Image();
    
    imageLoader.onload = () => {
      // 图片加载完成后再设置 src
      img.src = src;
      if (srcset) {
        img.srcset = srcset;
      }
      
      img.classList.add('loaded');
      img.removeAttribute('data-src');
      img.removeAttribute('data-srcset');
    };
    
    imageLoader.onerror = () => {
      img.classList.add('error');
    };
    
    imageLoader.src = src;
  }

  // 手动添加懒加载图片
  addImage(img) {
    this.observer.observe(img);
  }

  // 销毁观察器
  destroy() {
    this.observer.disconnect();
  }
}

// 智能预加载管理器
class IntelligentPreloader {
  constructor() {
    this.preloadCache = new Map();
    this.priorityQueue = [];
    this.isPreloading = false;
  }

  // 根据用户行为预测并预加载资源
  predictAndPreload(currentPage) {
    const predictions = this.getPredictions(currentPage);
    
    predictions.forEach(prediction => {
      this.addToQueue(prediction.resource, prediction.priority);
    });
    
    this.processQueue();
  }

  getPredictions(currentPage) {
    // 基于历史数据和用户行为的预测逻辑
    const commonRoutes = {
      '/': ['/products', '/about'],
      '/products': ['/product-detail', '/cart'],
      '/product-detail': ['/cart', '/checkout']
    };

    const nextPages = commonRoutes[currentPage] || [];
    
    return nextPages.map(page => ({
      resource: page,
      priority: this.calculatePriority(page, currentPage)
    }));
  }

  calculatePriority(targetPage, currentPage) {
    // 优先级计算算法
    const baseScore = 100;
    const userBehaviorData = this.getUserBehaviorData();
    
    // 基于用户历史行为调整优先级
    const transitionProbability = userBehaviorData[`${currentPage}->${targetPage}`] || 0.1;
    
    return Math.floor(baseScore * transitionProbability);
  }

  addToQueue(resource, priority) {
    this.priorityQueue.push({ resource, priority });
    this.priorityQueue.sort((a, b) => b.priority - a.priority);
  }

  async processQueue() {
    if (this.isPreloading || this.priorityQueue.length === 0) {
      return;
    }

    this.isPreloading = true;

    while (this.priorityQueue.length > 0) {
      const { resource, priority } = this.priorityQueue.shift();
      
      if (!this.preloadCache.has(resource)) {
        await this.preloadResource(resource);
      }
      
      // 控制预加载频率,避免影响当前页面性能
      await this.delay(100);
    }

    this.isPreloading = false;
  }

  async preloadResource(resource) {
    try {
      // 使用 fetch 进行预加载
      const response = await fetch(resource, {
        method: 'GET',
        cache: 'force-cache'
      });
      
      if (response.ok) {
        this.preloadCache.set(resource, {
          data: await response.text(),
          timestamp: Date.now()
        });
        
        console.log(`预加载完成: ${resource}`);
      }
    } catch (error) {
      console.warn(`预加载失败: ${resource}`, error);
    }
  }

  // 使用 link 标签进行资源预加载
  preloadWithLinkTag(href, as = 'fetch', crossorigin = 'anonymous') {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.href = href;
    link.as = as;
    if (crossorigin) {
      link.crossOrigin = crossorigin;
    }
    
    document.head.appendChild(link);
    
    return new Promise((resolve, reject) => {
      link.onload = resolve;
      link.onerror = reject;
    });
  }

  // 实现 prefetch
  prefetchResource(href) {
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = href;
    
    document.head.appendChild(link);
  }

  getUserBehaviorData() {
    // 从 localStorage 或服务器获取用户行为数据
    const data = localStorage.getItem('userBehaviorData');
    return data ? JSON.parse(data) : {};
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 组件级别的懒加载
class ComponentLazyLoader {
  constructor() {
    this.componentCache = new Map();
    this.importMap = new Map();
  }

  // 注册组件的动态导入函数
  registerComponent(name, importFunction) {
    this.importMap.set(name, importFunction);
  }

  // 懒加载组件
  async loadComponent(name) {
    if (this.componentCache.has(name)) {
      return this.componentCache.get(name);
    }

    const importFunction = this.importMap.get(name);
    if (!importFunction) {
      throw new Error(`Component ${name} not registered`);
    }

    try {
      const module = await importFunction();
      const component = module.default || module;
      
      this.componentCache.set(name, component);
      return component;
    } catch (error) {
      console.error(`Failed to load component ${name}:`, error);
      throw error;
    }
  }

  // 预加载高优先级组件
  async preloadCriticalComponents(componentNames) {
    const loadPromises = componentNames.map(name => 
      this.loadComponent(name).catch(error => {
        console.warn(`Failed to preload component ${name}:`, error);
      })
    );

    await Promise.allSettled(loadPromises);
  }
}

// 使用示例
const lazyLoader = new LazyImageLoader({ threshold: '100px' });
const preloader = new IntelligentPreloader();
const componentLoader = new ComponentLazyLoader();

// 注册组件
componentLoader.registerComponent('ProductList', () => import('./components/ProductList.js'));
componentLoader.registerComponent('UserProfile', () => import('./components/UserProfile.js'));

// 预加载关键组件
componentLoader.preloadCriticalComponents(['ProductList']);

使用 HTTP/2 多路复用

HTTP/2 是一种可以显著减少延迟、提高网页加载速度的协议。它支持多路复用(Multiplexing),允许多个请求和响应在一个连接中同时进行。

// HTTP/2 连接管理器
class HTTP2ConnectionManager {
  constructor(options = {}) {
    this.maxConcurrentStreams = options.maxConcurrentStreams || 100;
    this.connections = new Map();
    this.streamQueue = [];
    this.activeStreams = 0;
  }

  // 智能请求合并
  async batchRequests(requests) {
    const groupedRequests = this.groupRequestsByDomain(requests);
    const results = {};

    for (const [domain, domainRequests] of groupedRequests) {
      results[domain] = await this.processDomainRequests(domain, domainRequests);
    }

    return results;
  }

  groupRequestsByDomain(requests) {
    const grouped = new Map();

    requests.forEach(request => {
      const url = new URL(request.url);
      const domain = url.origin;

      if (!grouped.has(domain)) {
        grouped.set(domain, []);
      }

      grouped.get(domain).push(request);
    });

    return grouped;
  }

  async processDomainRequests(domain, requests) {
    // HTTP/2 连接复用
    const connection = await this.getConnection(domain);
    const chunks = this.chunkRequests(requests, this.maxConcurrentStreams);
    const results = [];

    for (const chunk of chunks) {
      const chunkResults = await Promise.all(
        chunk.map(request => this.makeRequest(request, connection))
      );
      results.push(...chunkResults);
    }

    return results;
  }

  chunkRequests(requests, chunkSize) {
    const chunks = [];
    for (let i = 0; i < requests.length; i += chunkSize) {
      chunks.push(requests.slice(i, i + chunkSize));
    }
    return chunks;
  }

  async getConnection(domain) {
    if (this.connections.has(domain)) {
      return this.connections.get(domain);
    }

    // 检查 HTTP/2 支持
    const connection = {
      domain,
      supportsHTTP2: await this.checkHTTP2Support(domain),
      activeStreams: 0,
      lastUsed: Date.now()
    };

    this.connections.set(domain, connection);
    return connection;
  }

  async checkHTTP2Support(domain) {
    try {
      // 使用 fetch 检查 HTTP/2 支持
      const response = await fetch(`${domain}/`, { 
        method: 'HEAD',
        cache: 'no-cache'
      });
      
      // 检查响应头中的协议版本
      return response.headers.get(':status') !== null;
    } catch (error) {
      console.warn(`无法检查 ${domain} 的 HTTP/2 支持:`, error);
      return false;
    }
  }

  async makeRequest(request, connection) {
    try {
      connection.activeStreams++;
      connection.lastUsed = Date.now();

      const response = await fetch(request.url, {
        method: request.method || 'GET',
        headers: request.headers || {},
        body: request.body,
        ...request.options
      });

      connection.activeStreams--;

      return {
        url: request.url,
        status: response.status,
        headers: Object.fromEntries(response.headers.entries()),
        data: await response.json().catch(() => response.text())
      };
    } catch (error) {
      connection.activeStreams--;
      throw new Error(`请求失败 ${request.url}: ${error.message}`);
    }
  }

  // 服务器推送资源预加载
  handleServerPush(pushStreams) {
    pushStreams.forEach(stream => {
      const { url, priority } = stream;
      
      // 缓存推送的资源
      this.cacheResource(url, stream.data, {
        priority,
        source: 'server-push',
        timestamp: Date.now()
      });
    });
  }

  cacheResource(url, data, metadata) {
    const cache = this.getResourceCache();
    cache.set(url, {
      data,
      metadata,
      expiresAt: Date.now() + (metadata.maxAge || 3600000) // 默认1小时
    });
  }

  getResourceCache() {
    if (!this.resourceCache) {
      this.resourceCache = new Map();
    }
    return this.resourceCache;
  }

  // 连接清理
  cleanupConnections() {
    const now = Date.now();
    const timeout = 300000; // 5分钟

    for (const [domain, connection] of this.connections) {
      if (now - connection.lastUsed > timeout && connection.activeStreams === 0) {
        this.connections.delete(domain);
        console.log(`清理闲置连接: ${domain}`);
      }
    }
  }
}

// HTTP/2 推送管理器
class ServerPushManager {
  constructor() {
    this.pushCache = new Map();
    this.criticalResources = new Set();
  }

  // 标记关键资源
  markCritical(resources) {
    resources.forEach(resource => {
      this.criticalResources.add(resource);
    });
  }

  // 生成推送清单
  generatePushManifest(entryPoint) {
    const manifest = {
      [entryPoint]: []
    };

    // 分析依赖关系
    const dependencies = this.analyzeDependencies(entryPoint);
    
    dependencies.forEach(dep => {
      if (this.criticalResources.has(dep.url)) {
        manifest[entryPoint].push({
          url: dep.url,
          type: dep.type,
          priority: dep.priority || 'high'
        });
      }
    });

    return manifest;
  }

  analyzeDependencies(entryPoint) {
    // 依赖分析逻辑(可以集成 Webpack 分析结果)
    return [
      { url: '/css/critical.css', type: 'style', priority: 'high' },
      { url: '/js/vendor.js', type: 'script', priority: 'high' },
      { url: '/js/app.js', type: 'script', priority: 'medium' }
    ];
  }
}

// 使用示例
const http2Manager = new HTTP2ConnectionManager({
  maxConcurrentStreams: 50
});

const pushManager = new ServerPushManager();
pushManager.markCritical(['/css/critical.css', '/js/vendor.js']);

// 批量请求示例
const requests = [
  { url: '/api/user', method: 'GET' },
  { url: '/api/products', method: 'GET' },
  { url: '/api/cart', method: 'GET' }
];

http2Manager.batchRequests(requests).then(results => {
  console.log('批量请求完成:', results);
});

优化服务器响应时间

优化服务器的响应时间可以减少浏览器等待时间,从而加快页面加载速度。常见的方法包括:

  • 启用服务器缓存:使用HTTP 缓存头,如 Cache-Control、ETag 等,減少不必要的服务器请求。

  • 使用CDN:利用内容分发网络(CDN)将静态资源分发到全球多个服务器节点,减少用户请求的延迟。

  • 优化数据库查询:缓存频繁访问的数据,减少数据库查询时间。

// 服务器性能监控器
class ServerPerformanceMonitor {
  constructor(options = {}) {
    this.thresholds = {
      responseTime: options.responseTimeThreshold || 200, // ms
      errorRate: options.errorRateThreshold || 0.01,     // 1%
      throughput: options.throughputThreshold || 1000    // requests/min
    };
    
    this.metrics = {
      responseTimes: [],
      errors: 0,
      requests: 0,
      startTime: Date.now()
    };

    this.alerts = [];
  }

  // 记录请求指标
  recordRequest(responseTime, isError = false) {
    this.metrics.requests++;
    this.metrics.responseTimes.push({
      time: responseTime,
      timestamp: Date.now()
    });

    if (isError) {
      this.metrics.errors++;
    }

    // 清理旧数据 (保留最近1小时)
    this.cleanOldMetrics();
    this.checkThresholds();
  }

  cleanOldMetrics() {
    const oneHourAgo = Date.now() - 3600000;
    this.metrics.responseTimes = this.metrics.responseTimes.filter(
      metric => metric.timestamp > oneHourAgo
    );
  }

  checkThresholds() {
    const avgResponseTime = this.getAverageResponseTime();
    const errorRate = this.getErrorRate();
    const throughput = this.getThroughput();

    if (avgResponseTime > this.thresholds.responseTime) {
      this.triggerAlert('HIGH_RESPONSE_TIME', {
        current: avgResponseTime,
        threshold: this.thresholds.responseTime
      });
    }

    if (errorRate > this.thresholds.errorRate) {
      this.triggerAlert('HIGH_ERROR_RATE', {
        current: errorRate,
        threshold: this.thresholds.errorRate
      });
    }

    if (throughput < this.thresholds.throughput) {
      this.triggerAlert('LOW_THROUGHPUT', {
        current: throughput,
        threshold: this.thresholds.throughput
      });
    }
  }

  getAverageResponseTime() {
    if (this.metrics.responseTimes.length === 0) return 0;
    
    const sum = this.metrics.responseTimes.reduce((acc, metric) => acc + metric.time, 0);
    return sum / this.metrics.responseTimes.length;
  }

  getErrorRate() {
    return this.metrics.requests > 0 ? this.metrics.errors / this.metrics.requests : 0;
  }

  getThroughput() {
    const durationMinutes = (Date.now() - this.metrics.startTime) / 60000;
    return durationMinutes > 0 ? this.metrics.requests / durationMinutes : 0;
  }

  triggerAlert(type, data) {
    const alert = {
      type,
      data,
      timestamp: Date.now()
    };

    this.alerts.push(alert);
    console.warn(`性能告警 [${type}]:`, data);

    // 发送告警通知
    this.sendAlert(alert);
  }

  async sendAlert(alert) {
    try {
      await fetch('/api/alerts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(alert)
      });
    } catch (error) {
      console.error('发送告警失败:', error);
    }
  }

  // 生成性能报告
  generateReport() {
    return {
      summary: {
        totalRequests: this.metrics.requests,
        errorCount: this.metrics.errors,
        averageResponseTime: this.getAverageResponseTime(),
        errorRate: this.getErrorRate(),
        throughput: this.getThroughput()
      },
      thresholds: this.thresholds,
      alerts: this.alerts.slice(-10), // 最近10个告警
      recommendations: this.getRecommendations()
    };
  }

  getRecommendations() {
    const recommendations = [];
    const avgResponseTime = this.getAverageResponseTime();
    const errorRate = this.getErrorRate();

    if (avgResponseTime > 500) {
      recommendations.push({
        type: 'PERFORMANCE',
        message: '响应时间过长,建议优化数据库查询或启用缓存'
      });
    }

    if (errorRate > 0.05) {
      recommendations.push({
        type: 'RELIABILITY',
        message: '错误率过高,建议检查错误日志并修复问题'
      });
    }

    return recommendations;
  }
}

// 响应时间优化工具
class ResponseTimeOptimizer {
  constructor() {
    this.cache = new Map();
    this.queryOptimizer = new DatabaseQueryOptimizer();
  }

  // 缓存装饰器
  cache(ttl = 300000) { // 默认5分钟
    return (target, propertyName, descriptor) => {
      const method = descriptor.value;

      descriptor.value = async function(...args) {
        const cacheKey = `${propertyName}:${JSON.stringify(args)}`;
        
        if (this.cache.has(cacheKey)) {
          const cached = this.cache.get(cacheKey);
          if (Date.now() - cached.timestamp < ttl) {
            return cached.data;
          }
        }

        const result = await method.apply(this, args);
        
        this.cache.set(cacheKey, {
          data: result,
          timestamp: Date.now()
        });

        return result;
      };

      return descriptor;
    };
  }

  // 数据库查询优化
  async optimizeQuery(query, params) {
    const optimizedQuery = this.queryOptimizer.optimize(query);
    const startTime = Date.now();
    
    try {
      const result = await this.executeQuery(optimizedQuery, params);
      const responseTime = Date.now() - startTime;
      
      // 记录查询性能
      this.recordQueryPerformance(query, responseTime);
      
      return result;
    } catch (error) {
      console.error('查询执行失败:', error);
      throw error;
    }
  }

  recordQueryPerformance(query, responseTime) {
    // 记录查询性能数据,用于后续优化分析
    console.log(`查询性能: ${query.substring(0, 50)}... - ${responseTime}ms`);
  }

  // 并行处理优化
  async parallelProcess(tasks) {
    const startTime = Date.now();
    
    try {
      const results = await Promise.all(tasks.map(async (task, index) => {
        const taskStartTime = Date.now();
        const result = await task();
        const taskDuration = Date.now() - taskStartTime;
        
        return {
          index,
          result,
          duration: taskDuration
        };
      }));

      const totalDuration = Date.now() - startTime;
      console.log(`并行处理完成: ${tasks.length} 个任务,总耗时 ${totalDuration}ms`);
      
      return results.sort((a, b) => a.index - b.index).map(r => r.result);
    } catch (error) {
      console.error('并行处理失败:', error);
      throw error;
    }
  }
}

// 数据库查询优化器
class DatabaseQueryOptimizer {
  optimize(query) {
    let optimizedQuery = query;
    
    // 添加适当的索引提示
    optimizedQuery = this.addIndexHints(optimizedQuery);
    
    // 优化 JOIN 顺序
    optimizedQuery = this.optimizeJoins(optimizedQuery);
    
    // 添加查询限制
    optimizedQuery = this.addQueryLimits(optimizedQuery);
    
    return optimizedQuery;
  }

  addIndexHints(query) {
    // 简单的索引提示添加逻辑
    return query;
  }

  optimizeJoins(query) {
    // JOIN 优化逻辑
    return query;
  }

  addQueryLimits(query) {
    // 添加合理的 LIMIT
    if (!query.toLowerCase().includes('limit') && 
        query.toLowerCase().includes('select')) {
      return query + ' LIMIT 1000';
    }
    return query;
  }
}

// 使用示例
const monitor = new ServerPerformanceMonitor({
  responseTimeThreshold: 150,
  errorRateThreshold: 0.02
});

const optimizer = new ResponseTimeOptimizer();

// Express 中间件示例
function performanceMiddleware(req, res, next) {
  const startTime = Date.now();

  res.on('finish', () => {
    const responseTime = Date.now() - startTime;
    const isError = res.statusCode >= 400;
    
    monitor.recordRequest(responseTime, isError);
  });

  next();
}