前端资源优化之【资源缓存】

97 阅读19分钟

资源缓存

资源缓存是提升 Web 应用性能的关键技术之一。通过合理的缓存策略,可以显著减少网络请求,提升页面加载速度,改善用户体验。本章将深入探讨各种缓存机制的实现和优化策略。

强缓存

强缓存是一种缓存机制,当浏览器请求资源时,如果命中强缓存,则直接从缓存中获取资源,不会向服务器发送请求。

强缓存的工作流程如下:

graph TD
    A["浏览器请求资源"] --> B{检查本地缓存}
    
    B -->|缓存不存在| C["向服务器发送请求"]
    B -->|缓存存在| D{检查缓存是否过期}
    
    D --> E["检查 Expires 头部"]
    D --> F["检查 Cache-Control 头部"]
    
    E --> G{当前时间 < Expires?}
    F --> H{当前时间 < max-age?}
    
    G -->|是| I["缓存有效"]
    G -->|否| J["缓存过期"]
    H -->|是| I
    H -->|否| J
    
    I --> K["直接使用本地缓存<br/>🚀 不发送网络请求"]
    J --> C
    
    C --> L["服务器处理请求"]
    L --> M["服务器返回资源<br/>+ 200 OK<br/>+ Expires/Cache-Control 头部"]
    M --> N["浏览器缓存资源"]
    N --> O["返回资源给用户"]
    K --> O
    
    subgraph "强缓存特点"
        P["✅ 不发送网络请求"]
        Q["✅ 响应速度快"]
        R["✅ 节省带宽"]
        S["⚠️ 可能返回过期内容"]
    end
    
    subgraph "缓存控制方式"
        T["Expires: 绝对时间<br/>HTTP/1.0"]
        U["Cache-Control: 相对时间<br/>HTTP/1.1 (推荐)"]
    end
    
    style A fill:#e3f2fd
    style I fill:#c8e6c9
    style K fill:#c8e6c9
    style O fill:#f3e5f5
    style C fill:#fff3e0
    style M fill:#fff3e0

Expire

Expire 是 HTTP/1.0 中的缓存控制头,用于指定资源的过期时间。

中文释义为:到期,表示缓存的过期时间。expire 是 HTTP 1.0 提出的,它描述的是一个绝对时间,该时间由服务端返回。因为 expire 值是一个固定时间,因此会受本地时间的影响,如果在缓存期间我们修改了本地时间,可能会导致缓存失效。

通常表示如下:

Expires: Wed, 11 May 2018 07:20:00 GMT

// Express.js 中设置 Expire 头
const express = require('express');
const app = express();

// Expire 缓存中间件
function setExpireHeaders(req, res, next) {
  const oneYear = 365 * 24 * 60 * 60 * 1000; // 一年的毫秒数
  const expireDate = new Date(Date.now() + oneYear);
  
  // 设置静态资源的过期时间
  if (req.url.match(/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$/)) {
    res.set('Expires', expireDate.toUTCString());
    res.set('Cache-Control', `max-age=${oneYear / 1000}`);
  }
  
  next();
}

// 应用中间件
app.use(setExpireHeaders);

// 静态文件服务
app.use('/static', express.static('public', {
  maxAge: '1y', // 设置一年的缓存时间
  etag: true,
  lastModified: true
}));

// 不同类型资源的缓存策略
const cacheStrategies = {
  // 长期缓存资源(带版本号的静态资源)
  longTerm: {
    pattern: /\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)\?v=[\w\d]+$/,
    maxAge: 365 * 24 * 60 * 60, // 1年
    cacheControl: 'public, max-age=31536000, immutable'
  },
  
  // 中期缓存资源
  mediumTerm: {
    pattern: /\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$/,
    maxAge: 30 * 24 * 60 * 60, // 30天
    cacheControl: 'public, max-age=2592000'
  },
  
  // 短期缓存资源
  shortTerm: {
    pattern: /\.(html|htm)$/,
    maxAge: 5 * 60, // 5分钟
    cacheControl: 'public, max-age=300, must-revalidate'
  },
  
  // 不缓存资源
  noCache: {
    pattern: /\/api\//,
    maxAge: 0,
    cacheControl: 'no-cache, no-store, must-revalidate'
  }
};

// 智能缓存中间件
function intelligentCacheMiddleware(req, res, next) {
  for (const [strategyName, strategy] of Object.entries(cacheStrategies)) {
    if (strategy.pattern.test(req.url)) {
      const expireDate = new Date(Date.now() + strategy.maxAge * 1000);
      
      res.set('Expires', expireDate.toUTCString());
      res.set('Cache-Control', strategy.cacheControl);
      
      // 添加缓存策略标识
      res.set('X-Cache-Strategy', strategyName);
      
      console.log(`应用缓存策略 [${strategyName}] 到资源: ${req.url}`);
      break;
    }
  }
  
  next();
}

app.use(intelligentCacheMiddleware);

Cache-Control

Cache-Control 是 HTTP/1.1 中更强大和灵活的缓存控制头,可以精确控制缓存行为。

中文释义为:缓存控制。cache-control 是 HTTP 1.1提出的,它描述的是一个相对时间,该相对时间由服务端返回.

表示如下:

Cache-Control: max-age=315360000

该属性还包括访问性及缓存方式设置,列举如下;

  • no-cache 存储在本地缓存取中,只是在与服务器进行新鲜度再验证之前,缓存无法使用。

  • no-store 不缓存资源到本地

  • public 可被所有用户缓存,多用户进行共享,包括终端或 CDN 等中间代理服务器

  • private 仅能被浏览器客户端缓存,属于私有缓存,不允许中间代理服务器缓存相关资源

// Cache-Control 指令管理器
class CacheControlManager {
  constructor() {
    this.directives = new Map();
    this.presets = this.initializePresets();
  }

  initializePresets() {
    return {
      // 静态资源预设
      static: {
        public: true,
        maxAge: 31536000, // 1年
        immutable: true
      },
      
      // API 响应预设
      api: {
        private: true,
        maxAge: 300, // 5分钟
        mustRevalidate: true
      },
      
      // HTML 页面预设
      html: {
        public: true,
        maxAge: 3600, // 1小时
        mustRevalidate: true
      },
      
      // 无缓存预设
      noCache: {
        noCache: true,
        noStore: true,
        mustRevalidate: true
      }
    };
  }

  // 构建 Cache-Control 头
  buildCacheControl(options = {}) {
    const directives = [];

    // 可见性指令
    if (options.public) directives.push('public');
    if (options.private) directives.push('private');

    // 过期指令
    if (options.maxAge !== undefined) {
      directives.push(`max-age=${options.maxAge}`);
    }
    if (options.sMaxAge !== undefined) {
      directives.push(`s-maxage=${options.sMaxAge}`);
    }

    // 重新验证指令
    if (options.mustRevalidate) directives.push('must-revalidate');
    if (options.proxyRevalidate) directives.push('proxy-revalidate');

    // 缓存控制指令
    if (options.noCache) directives.push('no-cache');
    if (options.noStore) directives.push('no-store');
    if (options.noTransform) directives.push('no-transform');

    // HTTP/2 Push 指令
    if (options.immutable) directives.push('immutable');

    return directives.join(', ');
  }

  // 应用预设配置
  applyPreset(presetName, customOptions = {}) {
    const preset = this.presets[presetName];
    if (!preset) {
      throw new Error(`未知的预设配置: ${presetName}`);
    }

    const mergedOptions = { ...preset, ...customOptions };
    return this.buildCacheControl(mergedOptions);
  }

  // 动态缓存策略
  getDynamicCacheStrategy(resourceType, userAgent, requestTime) {
    const strategies = {
      // 移动端优化策略
      mobile: {
        css: { public: true, maxAge: 86400, immutable: true }, // 1天
        js: { public: true, maxAge: 86400, immutable: true },  // 1天
        image: { public: true, maxAge: 604800 }                 // 7天
      },
      
      // 桌面端策略
      desktop: {
        css: { public: true, maxAge: 2592000, immutable: true }, // 30天
        js: { public: true, maxAge: 2592000, immutable: true },  // 30天
        image: { public: true, maxAge: 2592000 }                 // 30天
      }
    };

    const deviceType = this.detectDeviceType(userAgent);
    const strategy = strategies[deviceType] || strategies.desktop;
    
    return this.buildCacheControl(strategy[resourceType] || strategy.css);
  }

  detectDeviceType(userAgent) {
    const mobilePattern = /Mobile|Android|iPhone|iPad/i;
    return mobilePattern.test(userAgent) ? 'mobile' : 'desktop';
  }

  // 条件缓存
  getConditionalCacheControl(conditions) {
    const { isAuthenticated, resourceSize, bandwidth } = conditions;
    
    let options = { public: true, maxAge: 3600 };

    // 认证用户使用私有缓存
    if (isAuthenticated) {
      options.public = false;
      options.private = true;
      options.maxAge = 1800; // 30分钟
    }

    // 大文件减少缓存时间
    if (resourceSize > 1024 * 1024) { // 1MB
      options.maxAge = Math.max(options.maxAge / 2, 300);
    }

    // 低带宽用户增加缓存时间
    if (bandwidth && bandwidth < 1000000) { // 1Mbps
      options.maxAge *= 2;
    }

    return this.buildCacheControl(options);
  }
}

// 使用示例
const cacheManager = new CacheControlManager();

// Express 中间件实现
function advancedCacheMiddleware(req, res, next) {
  const userAgent = req.get('User-Agent') || '';
  const resourceType = getResourceType(req.url);
  
  // 根据资源类型和用户代理设置缓存
  const cacheControl = cacheManager.getDynamicCacheStrategy(
    resourceType, 
    userAgent, 
    Date.now()
  );
  
  res.set('Cache-Control', cacheControl);
  
  // 添加调试信息
  if (process.env.NODE_ENV === 'development') {
    res.set('X-Cache-Debug', JSON.stringify({
      resourceType,
      userAgent: userAgent.substring(0, 50),
      cacheControl
    }));
  }
  
  next();
}

function getResourceType(url) {
  if (/\.(css)$/.test(url)) return 'css';
  if (/\.(js)$/.test(url)) return 'js';
  if (/\.(png|jpg|jpeg|gif|webp|svg)$/.test(url)) return 'image';
  if (/\.(woff|woff2|ttf|eot)$/.test(url)) return 'font';
  return 'html';
}

协商缓存

协商缓存是一种需要向服务器确认的缓存机制。当强缓存失效时,浏览器会向服务器发送请求,通过比较资源的标识符来判断是否需要更新缓存。

协商缓存的工作流程如下:

graph TD
    A["客户端首次请求资源"] --> B["服务器处理请求"]
    B --> C["服务器返回资源内容<br/>+ 200 OK<br/>+ Last-Modified: 文件修改时间<br/>+ ETag: 内容哈希值"]
    C --> D["客户端缓存资源<br/>及验证信息"]
    
    D --> E["客户端再次请求<br/>同一资源"]
    E --> F["发送条件请求头<br/>If-Modified-Since: 缓存的修改时间<br/>If-None-Match: 缓存的ETag值"]
    
    F --> G["服务器验证资源"]
    G --> H{资源是否<br/>已更新?}
    
    H -->|未更新| I["304 Not Modified<br/>资源未修改"]
    H -->|已更新| J["200 OK<br/>返回新资源<br/>+ 新的Last-Modified<br/>+ 新的ETag"]
    
    I --> K["客户端使用缓存<br/>节省带宽和时间"]
    J --> L["客户端更新缓存<br/>使用新资源"]
    
    K --> M["完成请求"]
    L --> M
    
    subgraph "验证方式"
        N["Last-Modified 验证<br/>比较文件修改时间"]
        O["ETag 验证<br/>比较内容哈希值"]
    end
    
    G -.-> N
    G -.-> O
    
    style A fill:#e3f2fd
    style C fill:#e8f5e8
    style I fill:#c8e6c9
    style J fill:#fff3e0
    style K fill:#c8e6c9
    style L fill:#fff3e0
    style M fill:#f3e5f5

Last-Modified 与 If-Modified-Since

基于文件修改时间的协商缓存机制。

Last-Modified 由上一次请求的响应头返回,且该值会在本次请求中,通过请求头 If-Modified-Since 传递给服务端,服务端通过If-Modified-Since 与资源的修改时间进行对比,若在此日期后资源有更新,则将新的资源发送给客户端。

不过,通过文件的修改时间来判断资源是否更新是不明智的,因为很多时候文件更新时间变了,但文件内容未发生更改。

这样一来,就出现了 ETag 与If-None-Match。

// Last-Modified 缓存实现
class LastModifiedCache {
  constructor(options = {}) {
    this.enabled = options.enabled !== false;
    this.precision = options.precision || 1000; // 精度为秒
  }

  // 设置 Last-Modified 头
  setLastModified(res, filePath) {
    if (!this.enabled) return;

    try {
      const fs = require('fs');
      const stats = fs.statSync(filePath);
      const lastModified = stats.mtime;
      
      // 去掉毫秒精度,避免精度问题
      const roundedTime = new Date(
        Math.floor(lastModified.getTime() / this.precision) * this.precision
      );
      
      res.set('Last-Modified', roundedTime.toUTCString());
      return roundedTime;
    } catch (error) {
      console.error('设置 Last-Modified 失败:', error);
      return null;
    }
  }

  // 检查 If-Modified-Since
  checkIfModifiedSince(req, filePath) {
    if (!this.enabled) return true;

    const ifModifiedSince = req.get('If-Modified-Since');
    if (!ifModifiedSince) return true;

    try {
      const fs = require('fs');
      const stats = fs.statSync(filePath);
      const lastModified = stats.mtime;
      const clientTime = new Date(ifModifiedSince);
      
      // 去掉毫秒精度进行比较
      const serverTime = new Date(
        Math.floor(lastModified.getTime() / this.precision) * this.precision
      );
      
      return serverTime > clientTime;
    } catch (error) {
      console.error('检查 If-Modified-Since 失败:', error);
      return true;
    }
  }

  // 中间件实现
  middleware() {
    return (req, res, next) => {
      const originalSendFile = res.sendFile;
      
      res.sendFile = function(path, options, callback) {
        // 设置 Last-Modified
        const lastModified = this.setLastModified(res, path);
        
        // 检查是否需要返回 304
        if (!this.checkIfModifiedSince(req, path)) {
          res.status(304).end();
          return;
        }
        
        // 调用原始的 sendFile 方法
        return originalSendFile.call(this, path, options, callback);
      }.bind(this);
      
      next();
    };
  }
}

// 文件监控缓存管理器
class FileWatcherCache {
  constructor() {
    this.fileWatchers = new Map();
    this.fileModificationTimes = new Map();
    this.setupFileWatcher();
  }

  setupFileWatcher() {
    const chokidar = require('chokidar');
    
    // 监控静态文件目录
    this.watcher = chokidar.watch(['public/**/*', 'dist/**/*'], {
      ignored: /node_modules/,
      persistent: true
    });

    this.watcher.on('change', (filePath) => {
      this.updateFileModificationTime(filePath);
      this.notifyClients(filePath);
    });

    this.watcher.on('add', (filePath) => {
      this.updateFileModificationTime(filePath);
    });
  }

  updateFileModificationTime(filePath) {
    try {
      const fs = require('fs');
      const stats = fs.statSync(filePath);
      this.fileModificationTimes.set(filePath, stats.mtime);
      
      console.log(`文件更新: ${filePath} at ${stats.mtime}`);
    } catch (error) {
      console.error('更新文件修改时间失败:', error);
    }
  }

  getFileModificationTime(filePath) {
    return this.fileModificationTimes.get(filePath);
  }

  // 通知客户端文件已更新(用于热更新)
  notifyClients(filePath) {
    // 实现 WebSocket 通知逻辑
    if (this.wsServer) {
      this.wsServer.clients.forEach(client => {
        if (client.readyState === 1) { // WebSocket.OPEN
          client.send(JSON.stringify({
            type: 'fileChanged',
            filePath,
            timestamp: Date.now()
          }));
        }
      });
    }
  }

  setWebSocketServer(wsServer) {
    this.wsServer = wsServer;
  }

  destroy() {
    if (this.watcher) {
      this.watcher.close();
    }
  }
}

ETag 与 If-None-Match

基于内容哈希的协商缓存机制,更加精确和可靠。

不同于 Last-Modified,Etag 通过计算文件指纹,与请求传递过来的If-None-Match 进行对比,若值不等,则将新的资源发送给客户端。

值得一提的是,通常为了减轻服务器压力,并不会完整计算文件 hash 值作为 Etag,并且有些时候 Etag 的表现会退化为 Last-Modified(当指纹计算为文件更新时间时)。那为什么我们通常还是要选用 Etag 呢?原因有一下几点:

  • 文件也许会发生周期性的更改,但内容并无变化,这时我们希望客户端认为这个文件是未变的;

  • 文件修改频繁,比如在秒以内的时间进行修改,由于If-Modified-Since 能读取到的时间精度为S,因此这种场景下If-Modified-Since 无法正常使用;

  • 某些服务器不能精确获得文件的最后修改时间。

// ETag 缓存管理器
class ETagCache {
  constructor(options = {}) {
    this.algorithm = options.algorithm || 'md5';
    this.weak = options.weak !== false;
    this.cache = new Map();
    this.maxCacheSize = options.maxCacheSize || 1000;
  }

  // 生成 ETag
  generateETag(content, filePath = null) {
    const crypto = require('crypto');
    
    // 如果是文件路径,读取文件内容
    if (filePath && !content) {
      try {
        const fs = require('fs');
        content = fs.readFileSync(filePath);
      } catch (error) {
        console.error('读取文件失败:', error);
        return null;
      }
    }

    // 生成哈希
    const hash = crypto
      .createHash(this.algorithm)
      .update(content)
      .digest('hex');

    // 返回弱 ETag 或强 ETag
    return this.weak ? `W/"${hash}"` : `"${hash}"`;
  }

  // 缓存 ETag
  cacheETag(key, etag) {
    // 限制缓存大小
    if (this.cache.size >= this.maxCacheSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, {
      etag,
      timestamp: Date.now()
    });
  }

  // 获取缓存的 ETag
  getCachedETag(key) {
    const cached = this.cache.get(key);
    return cached ? cached.etag : null;
  }

  // 检查 If-None-Match
  checkIfNoneMatch(req, etag) {
    const ifNoneMatch = req.get('If-None-Match');
    if (!ifNoneMatch) return false;

    // 处理多个 ETag
    const clientETags = ifNoneMatch.split(',').map(tag => tag.trim());
    
    // 检查是否匹配
    return clientETags.includes(etag) || clientETags.includes('*');
  }

  // Express 中间件
  middleware(options = {}) {
    return (req, res, next) => {
      const originalSend = res.send;
      const originalSendFile = res.sendFile;

      // 拦截 send 方法
      res.send = function(body) {
        if (res.get('ETag')) {
          return originalSend.call(this, body);
        }

        const etag = this.generateETag(body);
        if (etag) {
          res.set('ETag', etag);
          
          if (this.checkIfNoneMatch(req, etag)) {
            res.status(304).end();
            return;
          }
        }

        return originalSend.call(this, body);
      }.bind(this);

      // 拦截 sendFile 方法
      res.sendFile = function(path, options, callback) {
        const cacheKey = `file:${path}`;
        let etag = this.getCachedETag(cacheKey);

        if (!etag) {
          etag = this.generateETag(null, path);
          if (etag) {
            this.cacheETag(cacheKey, etag);
          }
        }

        if (etag) {
          res.set('ETag', etag);
          
          if (this.checkIfNoneMatch(req, etag)) {
            res.status(304).end();
            return;
          }
        }

        return originalSendFile.call(this, path, options, callback);
      }.bind(this);

      next();
    };
  }
}

// 增量 ETag 计算器
class IncrementalETagCalculator {
  constructor() {
    this.chunks = new Map();
  }

  // 为大文件计算增量 ETag
  calculateIncrementalETag(filePath, chunkSize = 1024 * 1024) {
    return new Promise((resolve, reject) => {
      const crypto = require('crypto');
      const fs = require('fs');
      const hash = crypto.createHash('md5');
      
      const stream = fs.createReadStream(filePath, { 
        highWaterMark: chunkSize 
      });

      stream.on('data', (chunk) => {
        hash.update(chunk);
      });

      stream.on('end', () => {
        const etag = `"${hash.digest('hex')}"`;
        resolve(etag);
      });

      stream.on('error', reject);
    });
  }

  // 流式 ETag 计算
  createETagStream() {
    const crypto = require('crypto');
    const { Transform } = require('stream');
    const hash = crypto.createHash('md5');

    return new Transform({
      transform(chunk, encoding, callback) {
        hash.update(chunk);
        this.push(chunk);
        callback();
      },
      
      flush(callback) {
        const etag = `"${hash.digest('hex')}"`;
        this.emit('etag', etag);
        callback();
      }
    });
  }
}

// 智能缓存验证器
class SmartCacheValidator {
  constructor() {
    this.validators = new Map();
    this.setupDefaultValidators();
  }

  setupDefaultValidators() {
    // 文件大小验证器
    this.addValidator('filesize', (filePath, cachedETag) => {
      const fs = require('fs');
      try {
        const stats = fs.statSync(filePath);
        return stats.size > 0;
      } catch {
        return false;
      }
    });

    // 内容类型验证器
    this.addValidator('contentType', (filePath, cachedETag) => {
      const path = require('path');
      const ext = path.extname(filePath).toLowerCase();
      const textExtensions = ['.txt', '.html', '.css', '.js', '.json'];
      
      // 文本文件需要更精确的验证
      return !textExtensions.includes(ext);
    });
  }

  addValidator(name, validatorFn) {
    this.validators.set(name, validatorFn);
  }

  validate(filePath, cachedETag) {
    for (const [name, validator] of this.validators) {
      if (!validator(filePath, cachedETag)) {
        console.log(`缓存验证失败: ${name} for ${filePath}`);
        return false;
      }
    }
    return true;
  }
}

状态码

HTTP 缓存相关的状态码处理和优化。

// HTTP 状态码缓存管理器
class HTTPStatusCacheManager {
  constructor() {
    this.statusHandlers = new Map();
    this.setupDefaultHandlers();
  }

  setupDefaultHandlers() {
    // 304 Not Modified 处理
    this.addStatusHandler(304, (req, res, context) => {
      // 设置适当的缓存头
      if (context.etag) {
        res.set('ETag', context.etag);
      }
      if (context.lastModified) {
        res.set('Last-Modified', context.lastModified);
      }
      if (context.cacheControl) {
        res.set('Cache-Control', context.cacheControl);
      }

      // 添加性能指标
      res.set('X-Cache-Status', 'HIT');
      res.set('X-Response-Time', `${Date.now() - context.startTime}ms`);
      
      res.status(304).end();
    });

    // 200 OK with Cache 处理
    this.addStatusHandler(200, (req, res, context) => {
      if (context.etag) {
        res.set('ETag', context.etag);
      }
      if (context.lastModified) {
        res.set('Last-Modified', context.lastModified);
      }
      if (context.cacheControl) {
        res.set('Cache-Control', context.cacheControl);
      }

      res.set('X-Cache-Status', 'MISS');
      res.set('X-Response-Time', `${Date.now() - context.startTime}ms`);
    });

    // 412 Precondition Failed 处理
    this.addStatusHandler(412, (req, res, context) => {
      res.set('X-Cache-Status', 'PRECONDITION_FAILED');
      res.status(412).json({
        error: 'Precondition Failed',
        message: 'The resource has been modified',
        timestamp: new Date().toISOString()
      });
    });
  }

  addStatusHandler(statusCode, handler) {
    this.statusHandlers.set(statusCode, handler);
  }

  handleStatus(statusCode, req, res, context) {
    const handler = this.statusHandlers.get(statusCode);
    if (handler) {
      handler(req, res, context);
    } else {
      console.warn(`未处理的状态码: ${statusCode}`);
      res.status(statusCode).end();
    }
  }

  // 条件请求处理器
  processConditionalRequest(req, res, resourceInfo) {
    const context = {
      startTime: Date.now(),
      etag: resourceInfo.etag,
      lastModified: resourceInfo.lastModified,
      cacheControl: resourceInfo.cacheControl
    };

    // 检查 If-None-Match
    const ifNoneMatch = req.get('If-None-Match');
    if (ifNoneMatch && resourceInfo.etag) {
      const clientETags = ifNoneMatch.split(',').map(tag => tag.trim());
      if (clientETags.includes(resourceInfo.etag) || clientETags.includes('*')) {
        this.handleStatus(304, req, res, context);
        return true;
      }
    }

    // 检查 If-Modified-Since
    const ifModifiedSince = req.get('If-Modified-Since');
    if (ifModifiedSince && resourceInfo.lastModified) {
      const clientTime = new Date(ifModifiedSince);
      const resourceTime = new Date(resourceInfo.lastModified);
      
      if (resourceTime <= clientTime) {
        this.handleStatus(304, req, res, context);
        return true;
      }
    }

    // 检查 If-Match (强制匹配)
    const ifMatch = req.get('If-Match');
    if (ifMatch && resourceInfo.etag) {
      const clientETags = ifMatch.split(',').map(tag => tag.trim());
      if (!clientETags.includes(resourceInfo.etag) && !clientETags.includes('*')) {
        this.handleStatus(412, req, res, context);
        return true;
      }
    }

    // 检查 If-Unmodified-Since
    const ifUnmodifiedSince = req.get('If-Unmodified-Since');
    if (ifUnmodifiedSince && resourceInfo.lastModified) {
      const clientTime = new Date(ifUnmodifiedSince);
      const resourceTime = new Date(resourceInfo.lastModified);
      
      if (resourceTime > clientTime) {
        this.handleStatus(412, req, res, context);
        return true;
      }
    }

    // 资源需要返回
    this.handleStatus(200, req, res, context);
    return false;
  }
}

// 缓存性能监控器
class CachePerformanceMonitor {
  constructor() {
    this.metrics = {
      hits: 0,
      misses: 0,
      errors: 0,
      responseTimeSum: 0,
      requestCount: 0
    };
    
    this.detailedMetrics = new Map();
  }

  recordCacheHit(resourceType, responseTime) {
    this.metrics.hits++;
    this.metrics.responseTimeSum += responseTime;
    this.metrics.requestCount++;
    
    this.updateDetailedMetrics(resourceType, 'hit', responseTime);
  }

  recordCacheMiss(resourceType, responseTime) {
    this.metrics.misses++;
    this.metrics.responseTimeSum += responseTime;
    this.metrics.requestCount++;
    
    this.updateDetailedMetrics(resourceType, 'miss', responseTime);
  }

  recordCacheError(resourceType, error) {
    this.metrics.errors++;
    console.error('缓存错误:', error);
  }

  updateDetailedMetrics(resourceType, type, responseTime) {
    if (!this.detailedMetrics.has(resourceType)) {
      this.detailedMetrics.set(resourceType, {
        hits: 0,
        misses: 0,
        totalResponseTime: 0,
        requestCount: 0
      });
    }

    const typeMetrics = this.detailedMetrics.get(resourceType);
    typeMetrics[type === 'hit' ? 'hits' : 'misses']++;
    typeMetrics.totalResponseTime += responseTime;
    typeMetrics.requestCount++;
  }

  getHitRatio() {
    const total = this.metrics.hits + this.metrics.misses;
    return total > 0 ? (this.metrics.hits / total * 100).toFixed(2) : 0;
  }

  getAverageResponseTime() {
    return this.metrics.requestCount > 0 
      ? (this.metrics.responseTimeSum / this.metrics.requestCount).toFixed(2)
      : 0;
  }

  generateReport() {
    const report = {
      summary: {
        hitRatio: this.getHitRatio() + '%',
        averageResponseTime: this.getAverageResponseTime() + 'ms',
        totalRequests: this.metrics.requestCount,
        cacheHits: this.metrics.hits,
        cacheMisses: this.metrics.misses,
        errors: this.metrics.errors
      },
      byResourceType: {}
    };

    // 按资源类型统计
    for (const [resourceType, metrics] of this.detailedMetrics) {
      const total = metrics.hits + metrics.misses;
      report.byResourceType[resourceType] = {
        hitRatio: total > 0 ? (metrics.hits / total * 100).toFixed(2) + '%' : '0%',
        averageResponseTime: metrics.requestCount > 0 
          ? (metrics.totalResponseTime / metrics.requestCount).toFixed(2) + 'ms'
          : '0ms',
        requests: total
      };
    }

    return report;
  }

  reset() {
    this.metrics = {
      hits: 0,
      misses: 0,
      errors: 0,
      responseTimeSum: 0,
      requestCount: 0
    };
    this.detailedMetrics.clear();
  }
}

策略缓存

Service Worker

Service Worker 是现代 Web 应用中实现高级缓存策略的强大工具,它运行在浏览器后台,可以拦截网络请求并实现自定义的缓存逻辑。

注册
// Service Worker 注册管理器
class ServiceWorkerManager {
  constructor(options = {}) {
    this.swPath = options.swPath || '/sw.js';
    this.scope = options.scope || '/';
    this.updateViaCache = options.updateViaCache || 'imports';
    this.registration = null;
    this.isSupported = 'serviceWorker' in navigator;
  }

  // 注册 Service Worker
  async register() {
    if (!this.isSupported) {
      console.warn('Service Worker 不被支持');
      return false;
    }

    try {
      this.registration = await navigator.serviceWorker.register(this.swPath, {
        scope: this.scope,
        updateViaCache: this.updateViaCache
      });

      console.log('Service Worker 注册成功:', this.registration.scope);
      
      // 设置事件监听器
      this.setupEventListeners();
      
      // 检查更新
      this.checkForUpdates();
      
      return true;
    } catch (error) {
      console.error('Service Worker 注册失败:', error);
      return false;
    }
  }

  setupEventListeners() {
    if (!this.registration) return;

    // 监听控制器变化
    navigator.serviceWorker.addEventListener('controllerchange', () => {
      console.log('Service Worker 控制器已更改');
      this.onControllerChange();
    });

    // 监听消息
    navigator.serviceWorker.addEventListener('message', (event) => {
      this.handleMessage(event.data);
    });

    // 监听注册状态变化
    this.registration.addEventListener('updatefound', () => {
      console.log('发现 Service Worker 更新');
      this.handleUpdate();
    });
  }

  async checkForUpdates() {
    if (!this.registration) return;

    try {
      await this.registration.update();
      console.log('已检查 Service Worker 更新');
    } catch (error) {
      console.error('检查更新失败:', error);
    }
  }

  handleUpdate() {
    if (!this.registration.installing) return;

    const newWorker = this.registration.installing;
    
    newWorker.addEventListener('statechange', () => {
      if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
        // 新的 Service Worker 已安装,但旧的仍在控制
        this.promptUserToUpdate();
      }
    });
  }

  promptUserToUpdate() {
    if (confirm('发现新版本,是否立即更新?')) {
      this.skipWaiting();
    }
  }

  async skipWaiting() {
    if (!this.registration.waiting) return;

    // 发送跳过等待消息
    this.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
  }

  onControllerChange() {
    // 重新加载页面以使用新的 Service Worker
    window.location.reload();
  }

  handleMessage(data) {
    switch (data.type) {
      case 'CACHE_UPDATED':
        console.log('缓存已更新:', data.payload);
        break;
      case 'OFFLINE_READY':
        console.log('离线功能已准备就绪');
        break;
      case 'ERROR':
        console.error('Service Worker 错误:', data.payload);
        break;
      default:
        console.log('收到 Service Worker 消息:', data);
    }
  }

  // 向 Service Worker 发送消息
  async sendMessage(message) {
    if (!navigator.serviceWorker.controller) {
      console.warn('没有活动的 Service Worker');
      return;
    }

    return new Promise((resolve, reject) => {
      const messageChannel = new MessageChannel();
      
      messageChannel.port1.onmessage = (event) => {
        if (event.data.error) {
          reject(new Error(event.data.error));
        } else {
          resolve(event.data);
        }
      };

      navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
    });
  }

  // 获取缓存统计信息
  async getCacheStats() {
    try {
      return await this.sendMessage({ type: 'GET_CACHE_STATS' });
    } catch (error) {
      console.error('获取缓存统计失败:', error);
      return null;
    }
  }

  // 清理缓存
  async clearCache(cacheName) {
    try {
      return await this.sendMessage({ 
        type: 'CLEAR_CACHE', 
        cacheName 
      });
    } catch (error) {
      console.error('清理缓存失败:', error);
      return false;
    }
  }

  // 预缓存资源
  async precacheResources(urls) {
    try {
      return await this.sendMessage({ 
        type: 'PRECACHE_RESOURCES', 
        urls 
      });
    } catch (error) {
      console.error('预缓存失败:', error);
      return false;
    }
  }
}

// 使用示例
const swManager = new ServiceWorkerManager({
  swPath: '/service-worker.js',
  scope: '/',
  updateViaCache: 'none'
});

// 页面加载时注册
window.addEventListener('load', async () => {
  const success = await swManager.register();
  if (success) {
    console.log('Service Worker 注册成功');
    
    // 预缓存关键资源
    await swManager.precacheResources([
      '/css/critical.css',
      '/js/app.js',
      '/fonts/main.woff2'
    ]);
  }
});
Installing、Activated、Idle、Fetch 生命周期
// Service Worker 实现 (sw.js)
const CACHE_NAME = 'app-cache-v1';
const RUNTIME_CACHE = 'runtime-cache-v1';
const PRECACHE_URLS = [
  '/',
  '/css/app.css',
  '/js/app.js',
  '/images/logo.png',
  '/manifest.json'
];

// Service Worker 生命周期管理器
class ServiceWorkerLifecycleManager {
  constructor() {
    this.cacheNames = {
      precache: CACHE_NAME,
      runtime: RUNTIME_CACHE,
      images: 'images-cache-v1',
      api: 'api-cache-v1'
    };
    
    this.setupEventListeners();
  }

  setupEventListeners() {
    // 安装事件
    self.addEventListener('install', (event) => {
      console.log('Service Worker: Installing...');
      event.waitUntil(this.handleInstall());
    });

    // 激活事件
    self.addEventListener('activate', (event) => {
      console.log('Service Worker: Activating...');
      event.waitUntil(this.handleActivate());
    });

    // 网络请求拦截
    self.addEventListener('fetch', (event) => {
      event.respondWith(this.handleFetch(event.request));
    });

    // 消息处理
    self.addEventListener('message', (event) => {
      this.handleMessage(event);
    });

    // 后台同步
    self.addEventListener('sync', (event) => {
      this.handleBackgroundSync(event);
    });

    // 推送通知
    self.addEventListener('push', (event) => {
      this.handlePush(event);
    });
  }

  // Installing 阶段处理
  async handleInstall() {
    try {
      console.log('开始预缓存资源...');
      
      const cache = await caches.open(this.cacheNames.precache);
      
      // 预缓存关键资源
      await cache.addAll(PRECACHE_URLS);
      
      console.log('预缓存完成');
      
      // 跳过等待,立即激活
      await self.skipWaiting();
      
      // 通知客户端安装完成
      this.broadcastMessage({
        type: 'INSTALL_COMPLETE',
        timestamp: Date.now()
      });
      
    } catch (error) {
      console.error('安装阶段失败:', error);
      throw error;
    }
  }

  // Activated 阶段处理
  async handleActivate() {
    try {
      console.log('开始激活 Service Worker...');
      
      // 清理旧缓存
      await this.cleanupOldCaches();
      
      // 立即获取控制权
      await self.clients.claim();
      
      console.log('Service Worker 激活完成');
      
      // 通知客户端激活完成
      this.broadcastMessage({
        type: 'ACTIVATE_COMPLETE',
        timestamp: Date.now()
      });
      
    } catch (error) {
      console.error('激活阶段失败:', error);
      throw error;
    }
  }

  // 清理旧缓存
  async cleanupOldCaches() {
    const cacheWhitelist = Object.values(this.cacheNames);
    const cacheNames = await caches.keys();
    
    const deletePromises = cacheNames
      .filter(cacheName => !cacheWhitelist.includes(cacheName))
      .map(cacheName => {
        console.log('删除旧缓存:', cacheName);
        return caches.delete(cacheName);
      });
    
    await Promise.all(deletePromises);
  }

  // Fetch 事件处理
  async handleFetch(request) {
    const url = new URL(request.url);
    
    try {
      // 根据请求类型选择缓存策略
      if (this.isHTMLRequest(request)) {
        return await this.handleHTMLRequest(request);
      }
      
      if (this.isStaticAsset(request)) {
        return await this.handleStaticAsset(request);
      }
      
      if (this.isAPIRequest(request)) {
        return await this.handleAPIRequest(request);
      }
      
      if (this.isImageRequest(request)) {
        return await this.handleImageRequest(request);
      }
      
      // 默认网络优先策略
      return await this.networkFirst(request);
      
    } catch (error) {
      console.error('请求处理失败:', error);
      return await this.handleFetchError(request, error);
    }
  }

  // HTML 请求处理(网络优先,缓存回退)
  async handleHTMLRequest(request) {
    try {
      const networkResponse = await fetch(request);
      
      if (networkResponse.ok) {
        // 更新缓存
        const cache = await caches.open(this.cacheNames.runtime);
        cache.put(request, networkResponse.clone());
      }
      
      return networkResponse;
    } catch (error) {
      // 网络失败,返回缓存
      const cachedResponse = await caches.match(request);
      if (cachedResponse) {
        return cachedResponse;
      }
      
      // 返回离线页面
      return await caches.match('/offline.html');
    }
  }

  // 静态资源处理(缓存优先)
  async handleStaticAsset(request) {
    const cachedResponse = await caches.match(request);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    try {
      const networkResponse = await fetch(request);
      
      if (networkResponse.ok) {
        const cache = await caches.open(this.cacheNames.precache);
        cache.put(request, networkResponse.clone());
      }
      
      return networkResponse;
    } catch (error) {
      throw error;
    }
  }

  // API 请求处理(网络优先,短时间缓存)
  async handleAPIRequest(request) {
    try {
      const networkResponse = await fetch(request);
      
      if (networkResponse.ok && request.method === 'GET') {
        const cache = await caches.open(this.cacheNames.api);
        
        // 添加时间戳用于缓存过期检查
        const responseWithTimestamp = new Response(networkResponse.body, {
          status: networkResponse.status,
          statusText: networkResponse.statusText,
          headers: {
            ...networkResponse.headers,
            'sw-cached-at': Date.now().toString()
          }
        });
        
        cache.put(request, responseWithTimestamp.clone());
        
        return responseWithTimestamp;
      }
      
      return networkResponse;
    } catch (error) {
      // 检查缓存
      const cachedResponse = await this.getCachedAPIResponse(request);
      if (cachedResponse) {
        return cachedResponse;
      }
      
      throw error;
    }
  }

  // 获取缓存的 API 响应(检查过期时间)
  async getCachedAPIResponse(request) {
    const cachedResponse = await caches.match(request);
    
    if (!cachedResponse) return null;
    
    const cachedAt = cachedResponse.headers.get('sw-cached-at');
    if (!cachedAt) return cachedResponse;
    
    const age = Date.now() - parseInt(cachedAt);
    const maxAge = 5 * 60 * 1000; // 5分钟
    
    if (age > maxAge) {
      // 缓存过期,删除
      const cache = await caches.open(this.cacheNames.api);
      cache.delete(request);
      return null;
    }
    
    return cachedResponse;
  }

  // 图片请求处理(缓存优先,延迟加载)
  async handleImageRequest(request) {
    const cachedResponse = await caches.match(request);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    try {
      const networkResponse = await fetch(request);
      
      if (networkResponse.ok) {
        const cache = await caches.open(this.cacheNames.images);
        
        // 图片压缩处理(如果需要)
        const processedResponse = await this.processImageResponse(networkResponse);
        cache.put(request, processedResponse.clone());
        
        return processedResponse;
      }
      
      return networkResponse;
    } catch (error) {
      // 返回占位图片
      return await caches.match('/images/placeholder.png');
    }
  }

  // 图片响应处理
  async processImageResponse(response) {
    // 这里可以添加图片压缩、格式转换等逻辑
    return response;
  }

  // 请求类型判断
  isHTMLRequest(request) {
    return request.headers.get('accept')?.includes('text/html');
  }

  isStaticAsset(request) {
    return /\.(css|js|woff|woff2|ttf|eot)$/.test(new URL(request.url).pathname);
  }

  isAPIRequest(request) {
    return new URL(request.url).pathname.startsWith('/api/');
  }

  isImageRequest(request) {
    return /\.(png|jpg|jpeg|gif|webp|svg)$/.test(new URL(request.url).pathname);
  }

  // 网络优先策略
  async networkFirst(request) {
    try {
      const networkResponse = await fetch(request);
      return networkResponse;
    } catch (error) {
      const cachedResponse = await caches.match(request);
      return cachedResponse || new Response('Network error', { status: 408 });
    }
  }

  // 错误处理
  async handleFetchError(request, error) {
    console.error('Fetch 错误:', error);
    
    // 尝试返回缓存
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    
    // 返回错误页面
    return new Response('资源不可用', {
      status: 503,
      statusText: 'Service Unavailable'
    });
  }

  // 消息处理
  handleMessage(event) {
    const { type, ...payload } = event.data;
    
    switch (type) {
      case 'SKIP_WAITING':
        self.skipWaiting();
        break;
        
      case 'GET_CACHE_STATS':
        this.getCacheStats().then(stats => {
          event.ports[0].postMessage(stats);
        });
        break;
        
      case 'CLEAR_CACHE':
        this.clearSpecificCache(payload.cacheName).then(success => {
          event.ports[0].postMessage({ success });
        });
        break;
        
      case 'PRECACHE_RESOURCES':
        this.precacheResources(payload.urls).then(success => {
          event.ports[0].postMessage({ success });
        });
        break;
        
      default:
        console.log('未知消息类型:', type);
    }
  }

  // 获取缓存统计
  async getCacheStats() {
    const stats = {};
    
    for (const [name, cacheName] of Object.entries(this.cacheNames)) {
      try {
        const cache = await caches.open(cacheName);
        const keys = await cache.keys();
        stats[name] = {
          name: cacheName,
          count: keys.length,
          urls: keys.map(request => request.url)
        };
      } catch (error) {
        stats[name] = { error: error.message };
      }
    }
    
    return stats;
  }

  // 清理特定缓存
  async clearSpecificCache(cacheName) {
    try {
      return await caches.delete(cacheName);
    } catch (error) {
      console.error('清理缓存失败:', error);
      return false;
    }
  }

  // 预缓存资源
  async precacheResources(urls) {
    try {
      const cache = await caches.open(this.cacheNames.runtime);
      await cache.addAll(urls);
      return true;
    } catch (error) {
      console.error('预缓存失败:', error);
      return false;
    }
  }

  // 后台同步处理
  handleBackgroundSync(event) {
    if (event.tag === 'background-sync') {
      event.waitUntil(this.doBackgroundSync());
    }
  }

  async doBackgroundSync() {
    console.log('执行后台同步...');
    // 实现后台同步逻辑
  }

  // 推送通知处理
  handlePush(event) {
    const options = {
      body: event.data ? event.data.text() : 'No payload',
      icon: '/images/icon-192.png',
      badge: '/images/badge-72.png'
    };
    
    event.waitUntil(
      self.registration.showNotification('App Notification', options)
    );
  }

  // 广播消息给所有客户端
  async broadcastMessage(message) {
    const clients = await self.clients.matchAll();
    clients.forEach(client => {
      client.postMessage(message);
    });
  }
}

// 初始化 Service Worker 生命周期管理器
const lifecycleManager = new ServiceWorkerLifecycleManager();

现在我来为这些缓存策略生成相应的 Mermaid 流程图:

首先是完整的HTTP缓存请求流程图:

graph TD
    A["用户请求资源"] --> B{是否存在缓存?}
    
    B -->|no| C["向服务器请求"]
    B -->|yes| D{判断缓存是否过期?}
    
    D -->|no| E["直接使用缓存内容"]
    D -->|yes| F{判断ETag}
    
    F -->|no| G{判断Last-Modified}
    F -->|yes| H["向服务器请求<br/>If-None-Match"]
    
    G -->|no| C
    G -->|yes| I["向服务器请求<br/>If-Modified-Since"]
    
    H --> J{服务器返回200还是304}
    I --> J
    
    J -->|304| K["服务器返回304,读取本地缓存"]
    J -->|200| L["向服务器请求"]
    
    L --> M["请求响应,缓存防腐"]
    C --> M
    
    K --> N["返回展示资源"]
    M --> N
    E --> N
    
    style A fill:#e3f2fd
    style E fill:#c8e6c9
    style K fill:#c8e6c9
    style N fill:#f3e5f5
    style M fill:#fff3e0

HTTP缓存策略的组成结构:

graph TD
    A["HTTP 缓存策略"] --> B["强缓存"]
    A --> C["协商缓存"]
    
    B --> D["Expires<br/>指定过期时间"]
    B --> E["Cache-Control<br/>更灵活的缓存控制"]
    
    E --> E1["public/private<br/>缓存可见性"]
    E --> E2["max-age<br/>最大存活时间"]
    E --> E3["no-cache/no-store<br/>缓存控制"]
    E --> E4["must-revalidate<br/>强制重新验证"]
    
    C --> F["Last-Modified<br/>文件修改时间"]
    C --> G["ETag<br/>内容哈希值"]
    
    F --> F1["If-Modified-Since<br/>条件请求头"]
    G --> G1["If-None-Match<br/>条件请求头"]
    
    F1 --> H["304 Not Modified<br/>资源未修改"]
    G1 --> H
    F1 --> I["200 OK<br/>返回新资源"]
    G1 --> I
    
    H --> J["从缓存加载<br/>节省带宽"]
    I --> K["更新缓存<br/>下载新资源"]
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#e8f5e8
    style H fill:#c8e6c9
    style I fill:#ffecb3
    style J fill:#c8e6c9
    style K fill:#ffecb3

实现策略

Service Worker 提供了多种缓存策略,每种策略都有其适用的场景:

graph TD
    A["Service Worker<br/>拦截网络请求"] --> B{确定缓存策略}
    
    B --> C["仅缓存<br/>Cache Only"]
    B --> D["仅网络<br/>Network Only"]
    B --> E["缓存优先<br/>Cache First"]
    B --> F["网络优先<br/>Network First"]
    
    C --> C1["检查缓存"]
    C1 --> C2{缓存存在?}
    C2 -->|是| C3["返回缓存内容"]
    C2 -->|否| C4["返回错误"]
    
    D --> D1["直接网络请求"]
    D1 --> D2{请求成功?}
    D2 -->|是| D3["返回网络响应"]
    D2 -->|否| D4["返回网络错误"]
    
    E --> E1["检查缓存"]
    E1 --> E2{缓存存在?}
    E2 -->|是| E3["返回缓存内容<br/>后台更新缓存"]
    E2 -->|否| E4["网络请求"]
    E4 --> E5["缓存响应并返回"]
    
    F --> F1["网络请求"]
    F1 --> F2{请求成功?}
    F2 -->|是| F3["返回网络响应<br/>更新缓存"]
    F2 -->|否| F4["检查缓存"]
    F4 --> F5{缓存存在?}
    F5 -->|是| F6["返回缓存内容"]
    F5 -->|否| F7["返回错误"]
    
    subgraph "适用场景"
        S1["静态资源:仅缓存"]
        S2["API数据:仅网络"]
        S3["图片资源:缓存优先"]
        S4["HTML页面:网络优先"]
    end
    
    style A fill:#e3f2fd
    style C3 fill:#c8e6c9
    style D3 fill:#fff3e0
    style E3 fill:#c8e6c9
    style E5 fill:#fff3e0
    style F3 fill:#fff3e0
    style F6 fill:#c8e6c9
仅缓存

缓存优先策略,只从缓存中获取资源,适用于不经常变化的静态资源。

// 仅缓存策略实现
class CacheOnlyStrategy {
  constructor(cacheName) {
    this.cacheName = cacheName;
  }

  async handle(request) {
    const cache = await caches.open(this.cacheName);
    const cachedResponse = await cache.match(request);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    // 如果缓存中没有,返回错误
    throw new Error(`资源不在缓存中: ${request.url}`);
  }

  // 预缓存资源
  async precache(urls) {
    const cache = await caches.open(this.cacheName);
    
    const cachePromises = urls.map(async (url) => {
      try {
        const response = await fetch(url);
        if (response.ok) {
          await cache.put(url, response);
          console.log(`预缓存成功: ${url}`);
        }
      } catch (error) {
        console.error(`预缓存失败: ${url}`, error);
      }
    });
    
    await Promise.all(cachePromises);
  }
}

// 使用示例
const staticAssetsCache = new CacheOnlyStrategy('static-assets-v1');

// 在 Service Worker 中使用
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  // 对静态资源使用仅缓存策略
  if (url.pathname.match(/\.(css|js|woff2?|ttf|eot)$/)) {
    event.respondWith(staticAssetsCache.handle(event.request));
  }
});
仅限网络

网络优先策略,总是从网络获取最新资源,适用于实时性要求高的动态内容。

// 仅限网络策略实现
class NetworkOnlyStrategy {
  constructor(options = {}) {
    this.timeout = options.timeout || 10000; // 10秒超时
    this.retryAttempts = options.retryAttempts || 1;
  }

  async handle(request) {
    let lastError;
    
    for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.timeout);
        
        const response = await fetch(request, {
          signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        if (response.ok) {
          return response;
        }
        
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        
      } catch (error) {
        lastError = error;
        
        if (attempt < this.retryAttempts) {
          // 指数退避重试
          const delay = Math.pow(2, attempt) * 1000;
          await this.sleep(delay);
          console.log(`重试请求 (${attempt + 1}/${this.retryAttempts}): ${request.url}`);
        }
      }
    }
    
    throw lastError;
  }

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

  // 网络状态检测
  isOnline() {
    return navigator.onLine;
  }

  // 带网络检测的请求处理
  async handleWithNetworkCheck(request) {
    if (!this.isOnline()) {
      throw new Error('设备离线,无法访问网络');
    }
    
    return await this.handle(request);
  }
}
先缓存,然后回退到网络

缓存优先策略,先尝试从缓存获取,失败后再请求网络。

// 缓存优先策略实现
class CacheFirstStrategy {
  constructor(cacheName, options = {}) {
    this.cacheName = cacheName;
    this.maxAge = options.maxAge || 86400000; // 24小时
    this.updateCache = options.updateCache !== false;
  }

  async handle(request) {
    const cache = await caches.open(this.cacheName);
    const cachedResponse = await cache.match(request);
    
    // 检查缓存是否存在且有效
    if (cachedResponse && this.isCacheValid(cachedResponse)) {
      // 后台更新缓存(可选)
      if (this.updateCache) {
        this.updateCacheInBackground(request, cache);
      }
      
      return cachedResponse;
    }
    
    // 缓存失效或不存在,从网络获取
    try {
      const networkResponse = await fetch(request);
      
      if (networkResponse.ok) {
        // 更新缓存
        const responseWithTimestamp = this.addTimestamp(networkResponse);
        cache.put(request, responseWithTimestamp.clone());
        return responseWithTimestamp;
      }
      
      // 网络请求失败,如果有过期缓存也返回
      if (cachedResponse) {
        return cachedResponse;
      }
      
      throw new Error(`网络请求失败: ${networkResponse.status}`);
      
    } catch (error) {
      // 网络失败,返回缓存(即使过期)
      if (cachedResponse) {
        console.warn('网络失败,返回过期缓存:', request.url);
        return cachedResponse;
      }
      
      throw error;
    }
  }

  // 检查缓存是否有效
  isCacheValid(response) {
    const cachedAt = response.headers.get('sw-cached-at');
    if (!cachedAt) return true; // 没有时间戳的认为有效
    
    const age = Date.now() - parseInt(cachedAt);
    return age < this.maxAge;
  }

  // 添加时间戳到响应
  addTimestamp(response) {
    const headers = new Headers(response.headers);
    headers.set('sw-cached-at', Date.now().toString());
    
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: headers
    });
  }

  // 后台更新缓存
  async updateCacheInBackground(request, cache) {
    try {
      const response = await fetch(request);
      if (response.ok) {
        const responseWithTimestamp = this.addTimestamp(response);
        await cache.put(request, responseWithTimestamp);
        console.log('后台缓存更新成功:', request.url);
      }
    } catch (error) {
      console.warn('后台缓存更新失败:', error);
    }
  }
}
网络优先,回退到缓存

网络优先策略,优先从网络获取最新内容,失败后使用缓存。

// 网络优先策略实现
class NetworkFirstStrategy {
  constructor(cacheName, options = {}) {
    this.cacheName = cacheName;
    this.timeout = options.timeout || 3000; // 3秒超时
    this.maxCacheAge = options.maxCacheAge || 3600000; // 1小时
  }

  async handle(request) {
    const cache = await caches.open(this.cacheName);
    
    try {
      // 尝试网络请求
      const networkResponse = await this.fetchWithTimeout(request);
      
      if (networkResponse.ok) {
        // 更新缓存
        const responseToCache = this.addCacheHeaders(networkResponse);
        cache.put(request, responseToCache.clone());
        return responseToCache;
      }
      
      throw new Error(`网络响应错误: ${networkResponse.status}`);
      
    } catch (error) {
      console.warn('网络请求失败,尝试缓存:', error.message);
      
      // 网络失败,尝试缓存
      const cachedResponse = await cache.match(request);
      
      if (cachedResponse) {
        // 检查缓存是否过期
        if (this.isCacheExpired(cachedResponse)) {
          console.warn('缓存已过期,但仍返回:', request.url);
        }
        
        return cachedResponse;
      }
      
      // 缓存也没有,抛出错误
      throw new Error(`资源不可用: ${request.url}`);
    }
  }

  // 带超时的网络请求
  async fetchWithTimeout(request) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);
    
    try {
      const response = await fetch(request, {
        signal: controller.signal
      });
      clearTimeout(timeoutId);
      return response;
    } catch (error) {
      clearTimeout(timeoutId);
      throw error;
    }
  }

  // 添加缓存头信息
  addCacheHeaders(response) {
    const headers = new Headers(response.headers);
    headers.set('sw-cached-at', Date.now().toString());
    headers.set('sw-strategy', 'network-first');
    
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: headers
    });
  }

  // 检查缓存是否过期
  isCacheExpired(response) {
    const cachedAt = response.headers.get('sw-cached-at');
    if (!cachedAt) return false;
    
    const age = Date.now() - parseInt(cachedAt);
    return age > this.maxCacheAge;
  }
}
综合缓存策略路由器

在实际项目中,我们通常需要根据不同的资源类型和业务需求选择合适的缓存策略:

// 综合缓存策略路由器
class CacheStrategyRouter {
  constructor() {
    this.strategies = new Map();
    this.setupDefaultStrategies();
  }

  setupDefaultStrategies() {
    // 静态资源缓存策略
    this.strategies.set('static-assets', {
      pattern: /\.(css|js|woff2?|ttf|eot)$/,
      strategy: new CacheOnlyStrategy('static-assets-v1'),
      description: '静态资源使用仅缓存策略'
    });

    // 图片资源缓存策略
    this.strategies.set('images', {
      pattern: /\.(png|jpg|jpeg|gif|webp|svg)$/,
      strategy: new CacheFirstStrategy('images-cache-v1', { maxAge: 86400000 }),
      description: '图片资源使用缓存优先策略'
    });

    // HTML页面缓存策略
    this.strategies.set('pages', {
      pattern: /\.html?$/,
      strategy: new NetworkFirstStrategy('pages-cache-v1', { timeout: 2000 }),
      description: 'HTML页面使用网络优先策略'
    });

    // API接口缓存策略
    this.strategies.set('api', {
      pattern: /\/api\//,
      strategy: new NetworkOnlyStrategy({ timeout: 5000, retryAttempts: 2 }),
      description: 'API接口使用仅网络策略'
    });

    // 字体文件缓存策略
    this.strategies.set('fonts', {
      pattern: /\.(woff|woff2|ttf|eot)$/,
      strategy: new CacheFirstStrategy('fonts-cache-v1', { maxAge: 31536000000 }), // 1年
      description: '字体文件使用长期缓存策略'
    });
  }

  // 路由请求到对应的缓存策略
  async route(request) {
    const url = new URL(request.url);
    
    // 遍历所有策略,找到匹配的
    for (const [name, config] of this.strategies) {
      if (config.pattern.test(url.pathname)) {
        console.log(`使用缓存策略 [${name}]: ${config.description}`);
        
        try {
          return await config.strategy.handle(request);
        } catch (error) {
          console.error(`缓存策略 [${name}] 失败:`, error);
          
          // 回退到基本网络请求
          return await this.fallbackToNetwork(request);
        }
      }
    }

    // 默认策略:网络优先
    console.log('使用默认缓存策略:网络优先');
    const defaultStrategy = new NetworkFirstStrategy('default-cache-v1');
    return await defaultStrategy.handle(request);
  }

  // 回退到基本网络请求
  async fallbackToNetwork(request) {
    try {
      return await fetch(request);
    } catch (error) {
      return new Response('资源不可用', {
        status: 503,
        statusText: 'Service Unavailable'
      });
    }
  }

  // 添加自定义策略
  addStrategy(name, pattern, strategy, description) {
    this.strategies.set(name, {
      pattern,
      strategy,
      description
    });
  }

  // 移除策略
  removeStrategy(name) {
    return this.strategies.delete(name);
  }

  // 获取所有策略信息
  getStrategiesInfo() {
    const info = {};
    for (const [name, config] of this.strategies) {
      info[name] = {
        pattern: config.pattern.toString(),
        description: config.description
      };
    }
    return info;
  }
}

// Service Worker 中的使用示例
const cacheRouter = new CacheStrategyRouter();

// 自定义策略示例:视频文件使用特殊缓存策略
cacheRouter.addStrategy(
  'videos',
  /\.(mp4|webm|avi)$/,
  new CacheFirstStrategy('videos-cache-v1', { maxAge: 604800000 }), // 7天
  '视频文件使用缓存优先策略,7天有效期'
);

// 在 Service Worker 的 fetch 事件中使用
self.addEventListener('fetch', (event) => {
  // 只处理 GET 请求
  if (event.request.method !== 'GET') {
    return;
  }

  event.respondWith(
    cacheRouter.route(event.request).catch((error) => {
      console.error('缓存路由失败:', error);
      return fetch(event.request);
    })
  );
});

// 缓存预热示例
self.addEventListener('install', (event) => {
  event.waitUntil(
    Promise.all([
      // 预缓存关键静态资源
      cacheRouter.strategies.get('static-assets').strategy.precache([
        '/css/app.css',
        '/js/app.js',
        '/js/vendor.js'
      ]),
      
      // 预缓存关键图片
      cacheRouter.strategies.get('images').strategy.precache([
        '/images/logo.png',
        '/images/hero-banner.jpg'
      ])
    ])
  );
});

缓存技术方案实践

静态资源优化方案与思考

// 静态资源缓存优化方案
class StaticResourceCacheOptimizer {
  constructor() {
    this.cacheConfigs = this.initializeCacheConfigs();
    this.versionManager = new AssetVersionManager();
    this.compressionManager = new CompressionManager();
  }

  initializeCacheConfigs() {
    return {
      // 长期缓存资源(带版本号)
      longTerm: {
        pattern: /\.(css|js|png|jpg|jpeg|gif|woff|woff2|ttf|eot)\?v=[\w\d]+$/,
        maxAge: 31536000, // 1年
        cacheControl: 'public, max-age=31536000, immutable',
        strategy: 'cache-first'
      },
      
      // 中期缓存资源
      mediumTerm: {
        pattern: /\.(css|js|png|jpg|jpeg|gif|woff|woff2|ttf|eot)$/,
        maxAge: 2592000, // 30天
        cacheControl: 'public, max-age=2592000',
        strategy: 'cache-first'
      },
      
      // 短期缓存资源
      shortTerm: {
        pattern: /\.(html|htm)$/,
        maxAge: 300, // 5分钟
        cacheControl: 'public, max-age=300, must-revalidate',
        strategy: 'network-first'
      },
      
      // API 缓存
      api: {
        pattern: /\/api\//,
        maxAge: 60, // 1分钟
        cacheControl: 'private, max-age=60',
        strategy: 'network-first'
      }
    };
  }

  // 生成缓存策略
  generateCacheStrategy(request) {
    const url = new URL(request.url);
    
    for (const [name, config] of Object.entries(this.cacheConfigs)) {
      if (config.pattern.test(url.href)) {
        return {
          name,
          config,
          headers: this.generateCacheHeaders(config)
        };
      }
    }
    
    // 默认策略
    return {
      name: 'default',
      config: this.cacheConfigs.shortTerm,
      headers: this.generateCacheHeaders(this.cacheConfigs.shortTerm)
    };
  }

  generateCacheHeaders(config) {
    const headers = new Headers();
    headers.set('Cache-Control', config.cacheControl);
    
    if (config.maxAge) {
      const expireDate = new Date(Date.now() + config.maxAge * 1000);
      headers.set('Expires', expireDate.toUTCString());
    }
    
    return headers;
  }

  // 版本化资源处理
  processVersionedAssets(manifest) {
    const versionedAssets = new Map();
    
    Object.entries(manifest).forEach(([file, hash]) => {
      const versionedUrl = `${file}?v=${hash}`;
      versionedAssets.set(file, versionedUrl);
    });
    
    return versionedAssets;
  }

  // 资源预加载策略
  async implementPreloadStrategy(criticalResources) {
    const preloadPromises = criticalResources.map(async (resource) => {
      try {
        const link = document.createElement('link');
        link.rel = 'preload';
        link.href = resource.url;
        link.as = resource.type;
        
        if (resource.crossorigin) {
          link.crossOrigin = resource.crossorigin;
        }
        
        document.head.appendChild(link);
        
        // 等待预加载完成
        return new Promise((resolve, reject) => {
          link.onload = resolve;
          link.onerror = reject;
        });
      } catch (error) {
        console.error('预加载失败:', resource.url, error);
      }
    });
    
    await Promise.allSettled(preloadPromises);
  }

  // 智能缓存清理
  async performIntelligentCacheCleanup() {
    const cacheNames = await caches.keys();
    const currentTime = Date.now();
    
    for (const cacheName of cacheNames) {
      const cache = await caches.open(cacheName);
      const requests = await cache.keys();
      
      for (const request of requests) {
        const response = await cache.match(request);
        const cachedAt = response.headers.get('sw-cached-at');
        
        if (cachedAt) {
          const age = currentTime - parseInt(cachedAt);
          const maxAge = this.getMaxAgeForRequest(request);
          
          if (age > maxAge * 2) { // 超过最大年龄的2倍
            await cache.delete(request);
            console.log('清理过期缓存:', request.url);
          }
        }
      }
    }
  }

  getMaxAgeForRequest(request) {
    const strategy = this.generateCacheStrategy(request);
    return strategy.config.maxAge * 1000; // 转换为毫秒
  }
}

充分利用浏览器缓存机制

// 浏览器缓存机制优化器
class BrowserCacheOptimizer {
  constructor() {
    this.memoryCache = new MemoryCache();
    this.diskCache = new DiskCache();
    this.httpCache = new HTTPCache();
    this.setupCacheCoordination();
  }

  setupCacheCoordination() {
    // 内存缓存优先级最高
    this.cacheHierarchy = [
      this.memoryCache,
      this.diskCache,
      this.httpCache
    ];
  }

  // 分层缓存查找
  async findInCache(request) {
    for (const cache of this.cacheHierarchy) {
      const response = await cache.match(request);
      if (response) {
        // 更新上级缓存
        await this.updateUpperCaches(request, response, cache);
        return response;
      }
    }
    return null;
  }

  // 更新上级缓存
  async updateUpperCaches(request, response, sourceCache) {
    const sourceCacheIndex = this.cacheHierarchy.indexOf(sourceCache);
    
    for (let i = 0; i < sourceCacheIndex; i++) {
      const upperCache = this.cacheHierarchy[i];
      await upperCache.put(request, response.clone());
    }
  }

  // 智能缓存存储
  async storeInOptimalCache(request, response) {
    const resourceInfo = this.analyzeResource(request, response);
    const optimalCache = this.selectOptimalCache(resourceInfo);
    
    await optimalCache.put(request, response);
    
    // 根据访问频率决定是否存储到内存缓存
    if (resourceInfo.accessFrequency > 0.8) {
      await this.memoryCache.put(request, response.clone());
    }
  }

  analyzeResource(request, response) {
    const url = new URL(request.url);
    const contentType = response.headers.get('content-type') || '';
    const contentLength = parseInt(response.headers.get('content-length') || '0');
    
    return {
      url: url.href,
      path: url.pathname,
      contentType,
      size: contentLength,
      isStatic: this.isStaticResource(url.pathname),
      accessFrequency: this.getAccessFrequency(url.href),
      cacheability: this.assessCacheability(response)
    };
  }

  isStaticResource(pathname) {
    return /\.(css|js|png|jpg|jpeg|gif|webp|svg|woff|woff2|ttf|eot)$/.test(pathname);
  }

  getAccessFrequency(url) {
    // 从访问统计中获取频率
    const stats = this.getAccessStats(url);
    return stats ? stats.frequency : 0;
  }

  getAccessStats(url) {
    // 模拟访问统计数据
    return { frequency: Math.random() };
  }

  assessCacheability(response) {
    const cacheControl = response.headers.get('cache-control') || '';
    
    if (cacheControl.includes('no-store')) return 0;
    if (cacheControl.includes('no-cache')) return 0.2;
    if (cacheControl.includes('private')) return 0.5;
    if (cacheControl.includes('public')) return 1;
    
    return 0.7; // 默认可缓存性
  }

  selectOptimalCache(resourceInfo) {
    // 小的静态资源优先存储到内存缓存
    if (resourceInfo.isStatic && resourceInfo.size < 100 * 1024) { // 100KB
      return this.memoryCache;
    }
    
    // 大文件存储到磁盘缓存
    if (resourceInfo.size > 1024 * 1024) { // 1MB
      return this.diskCache;
    }
    
    // 默认使用HTTP缓存
    return this.httpCache;
  }

  // 缓存预热
  async warmupCache(urls) {
    const preloadPromises = urls.map(async (url) => {
      try {
        const request = new Request(url);
        const response = await fetch(request);
        
        if (response.ok) {
          await this.storeInOptimalCache(request, response);
          console.log(`缓存预热成功: ${url}`);
        }
      } catch (error) {
        console.error(`缓存预热失败: ${url}`, error);
      }
    });
    
    await Promise.allSettled(preloadPromises);
  }

  // 缓存失效处理
  async invalidateCache(pattern) {
    const invalidationPromises = this.cacheHierarchy.map(async (cache) => {
      await cache.invalidate(pattern);
    });
    
    await Promise.all(invalidationPromises);
  }

  // 生成缓存报告
  async generateCacheReport() {
    const reports = await Promise.all(
      this.cacheHierarchy.map(cache => cache.getReport())
    );
    
    return {
      summary: {
        totalCaches: this.cacheHierarchy.length,
        totalSize: reports.reduce((sum, report) => sum + report.size, 0),
        totalEntries: reports.reduce((sum, report) => sum + report.entries, 0)
      },
      details: reports,
      recommendations: this.generateRecommendations(reports)
    };
  }

  generateRecommendations(reports) {
    const recommendations = [];
    
    // 检查内存缓存使用率
    const memoryReport = reports[0];
    if (memoryReport.usageRatio > 0.9) {
      recommendations.push({
        type: 'memory_cache_full',
        message: '内存缓存使用率过高,建议清理或增加容量',
        priority: 'high'
      });
    }
    
    // 检查磁盘缓存大小
    const diskReport = reports[1];
    if (diskReport.size > 500 * 1024 * 1024) { // 500MB
      recommendations.push({
        type: 'disk_cache_large',
        message: '磁盘缓存过大,建议定期清理',
        priority: 'medium'
      });
    }
    
    return recommendations;
  }
}

// 使用示例
const cacheOptimizer = new BrowserCacheOptimizer();

// 应用启动时预热缓存
window.addEventListener('load', async () => {
  const criticalResources = [
    '/css/critical.css',
    '/js/vendor.js',
    '/js/app.js',
    '/images/logo.png'
  ];
  
  await cacheOptimizer.warmupCache(criticalResources);
  
  // 生成缓存报告
  const report = await cacheOptimizer.generateCacheReport();
  console.log('缓存状态报告:', report);
});

资源缓存最佳实践

缓存策略选择指南

根据不同类型的资源选择合适的缓存策略是优化Web应用性能的关键:

graph TD
    A["资源类型分析"] --> B{资源特征}
    
    B --> C["静态不变资源<br/>CSS/JS/字体"]
    B --> D["更新频繁资源<br/>HTML/API"]
    B --> E["大文件资源<br/>图片/视频"]
    B --> F["实时数据<br/>用户状态/通知"]
    
    C --> C1["强缓存 + 版本控制<br/>Cache-Control: max-age=31536000<br/>文件名带hash值"]
    D --> D1["协商缓存<br/>ETag + Last-Modified<br/>短期缓存 + 重新验证"]
    E --> E2["缓存优先策略<br/>Cache First<br/>延迟加载 + 压缩"]
    F --> F1["仅网络策略<br/>Network Only<br/>实时获取"]
    
    style C1 fill:#c8e6c9
    style D1 fill:#fff3e0
    style E2 fill:#e1f5fe
    style F1 fill:#fce4ec

实际应用场景

电商网站缓存策略
// 电商网站综合缓存策略
class ECommerceCacheStrategy {
  constructor() {
    this.setupCacheRoutes();
  }

  setupCacheRoutes() {
    // 商品静态资源 - 长期缓存
    this.addRoute('product-assets', {
      pattern: /\/(css|js|fonts)\//,
      strategy: 'cache-first',
      maxAge: 31536000, // 1年
      description: '静态资源长期缓存,配合版本控制'
    });

    // 商品图片 - 缓存优先
    this.addRoute('product-images', {
      pattern: /\/images\/products\//,
      strategy: 'cache-first',
      maxAge: 2592000, // 30天
      description: '商品图片缓存优先,定期更新'
    });

    // 商品列表API - 短期缓存
    this.addRoute('product-api', {
      pattern: /\/api\/products/,
      strategy: 'network-first',
      maxAge: 300, // 5分钟
      description: '商品API网络优先,短期缓存防止重复请求'
    });

    // 用户相关API - 仅网络
    this.addRoute('user-api', {
      pattern: /\/api\/user\//,
      strategy: 'network-only',
      description: '用户数据实时获取,不使用缓存'
    });

    // 购物车API - 仅网络
    this.addRoute('cart-api', {
      pattern: /\/api\/cart/,
      strategy: 'network-only',
      description: '购物车数据实时同步'
    });
  }

  // 实现具体的缓存逻辑
  addRoute(name, config) {
    console.log(`配置缓存路由 [${name}]: ${config.description}`);
  }
}

// 使用示例
const ecommerceCache = new ECommerceCacheStrategy();
新闻网站缓存策略
// 新闻网站缓存策略
class NewsCacheStrategy {
  constructor() {
    this.setupNewsCache();
  }

  setupNewsCache() {
    // 新闻文章 - 协商缓存
    this.configureRoute('articles', {
      pattern: /\/articles\//,
      headers: {
        'Cache-Control': 'public, max-age=3600, must-revalidate',
        'ETag': 'article-version',
        'Last-Modified': 'article-update-time'
      },
      description: '新闻文章使用协商缓存,保证时效性'
    });

    // 新闻图片 - 长期缓存
    this.configureRoute('news-images', {
      pattern: /\/news-images\//,
      headers: {
        'Cache-Control': 'public, max-age=86400, immutable'
      },
      description: '新闻图片长期缓存,不会修改'
    });

    // 首页内容 - 短期缓存
    this.configureRoute('homepage', {
      pattern: /^\/$/,
      headers: {
        'Cache-Control': 'public, max-age=300, must-revalidate'
      },
      description: '首页短期缓存,频繁更新'
    });
  }

  configureRoute(name, config) {
    console.log(`配置新闻缓存 [${name}]: ${config.description}`);
  }
}

缓存性能监控与优化

// 缓存性能监控器
class CachePerformanceMonitor {
  constructor() {
    this.metrics = {
      hitRate: 0,
      missRate: 0,
      averageResponseTime: 0,
      cacheSize: 0,
      errorRate: 0
    };
    
    this.thresholds = {
      minHitRate: 0.8,        // 最低命中率80%
      maxResponseTime: 200,   // 最大响应时间200ms
      maxCacheSize: 100,      // 最大缓存100MB
      maxErrorRate: 0.05      // 最大错误率5%
    };
  }

  // 实时监控缓存性能
  startMonitoring() {
    setInterval(() => {
      this.collectMetrics();
      this.analyzePerformance();
      this.generateRecommendations();
    }, 60000); // 每分钟检查一次
  }

  collectMetrics() {
    // 收集缓存指标
    this.metrics = {
      hitRate: this.calculateHitRate(),
      missRate: this.calculateMissRate(),
      averageResponseTime: this.calculateAverageResponseTime(),
      cacheSize: this.calculateCacheSize(),
      errorRate: this.calculateErrorRate()
    };
  }

  analyzePerformance() {
    const issues = [];

    if (this.metrics.hitRate < this.thresholds.minHitRate) {
      issues.push({
        type: 'low_hit_rate',
        message: `缓存命中率过低: ${(this.metrics.hitRate * 100).toFixed(1)}%`,
        recommendation: '检查缓存策略配置,增加缓存时间或优化缓存键值'
      });
    }

    if (this.metrics.averageResponseTime > this.thresholds.maxResponseTime) {
      issues.push({
        type: 'slow_response',
        message: `平均响应时间过长: ${this.metrics.averageResponseTime}ms`,
        recommendation: '优化缓存查找算法或增加内存缓存'
      });
    }

    if (this.metrics.cacheSize > this.thresholds.maxCacheSize) {
      issues.push({
        type: 'large_cache',
        message: `缓存大小超限: ${this.metrics.cacheSize}MB`,
        recommendation: '清理过期缓存或调整缓存策略'
      });
    }

    return issues;
  }

  generateRecommendations() {
    const recommendations = [];

    // 基于性能指标生成建议
    if (this.metrics.hitRate > 0.9) {
      recommendations.push('缓存策略效果良好,可以考虑增加缓存时间');
    } else if (this.metrics.hitRate < 0.6) {
      recommendations.push('考虑预加载关键资源或调整缓存策略');
    }

    if (this.metrics.averageResponseTime < 100) {
      recommendations.push('响应时间优秀,当前缓存配置适合');
    }

    return recommendations;
  }

  // 模拟指标计算方法
  calculateHitRate() { return Math.random() * 0.4 + 0.6; }
  calculateMissRate() { return 1 - this.calculateHitRate(); }
  calculateAverageResponseTime() { return Math.random() * 200 + 50; }
  calculateCacheSize() { return Math.random() * 150 + 20; }
  calculateErrorRate() { return Math.random() * 0.1; }
}

// 启动监控
const monitor = new CachePerformanceMonitor();
monitor.startMonitoring();

总结与建议

关键原则
  1. 分层缓存策略

    • 内存缓存:高频访问的小文件
    • 磁盘缓存:大文件和中频访问资源
    • 网络缓存:HTTP缓存头控制
  2. 资源分类管理

    • 静态资源:长期缓存 + 版本控制
    • 动态内容:协商缓存 + 短期失效
    • 实时数据:仅网络策略
  3. 性能监控

    • 缓存命中率监控
    • 响应时间分析
    • 缓存大小管理
    • 错误率跟踪
  4. 持续优化

    • 定期清理过期缓存
    • 根据访问模式调整策略
    • 监控用户体验指标
    • A/B测试不同缓存配置

通过合理的缓存策略设计和持续的性能监控,可以显著提升Web应用的加载速度和用户体验,同时减少服务器负载和带宽消耗。