pin_drop当前位置:知识文库 ❯ 图文

后端通信:18. 请求缓存策略 - 从入门到实践详解

教程简介

请求缓存是提升Web应用性能的关键手段。通过合理利用浏览器缓存、HTTP缓存头、Service Worker缓存以及前端内存缓存,可以显著减少网络请求次数、降低服务器负载、加快页面加载速度。本教程将全面讲解HTTP缓存机制、前端缓存策略、Service Worker缓存、缓存更新与失效等内容,帮助你构建高性能的缓存体系。

核心概念

缓存层次结构

代码示例

请求发出
  ↓
内存缓存(最快,容量最小)
  ↓
Service Worker缓存
  ↓
浏览器HTTP缓存(Disk Cache)
  ↓
CDN缓存
  ↓
服务器缓存(Redis/Memcached)
  ↓
数据库

HTTP缓存类型

类型 机制 是否发请求 响应速度 新鲜度保证
强缓存 Cache-Control / Expires 最快 过期前不验证
协商缓存 ETag / Last-Modified 是(304) 较快 每次验证

强缓存 vs 协商缓存流程

代码示例

强缓存流程:
浏览器请求 → 检查缓存 → 未过期 → 直接使用缓存(200 from cache)
                      → 已过期 → 发送请求 → 获取新资源

协商缓存流程:
浏览器请求 → 检查缓存 → 发送验证请求(带If-None-Match/If-Modified-Since)
                      → 服务器验证
                      → 未修改 → 304 Not Modified(使用缓存)
                      → 已修改 → 200 OK + 新资源

Cache-Control指令详解

指令 说明 示例
max-age=N 缓存有效期(秒) max-age=3600
no-cache 强制验证(可缓存但需验证) no-cache
no-store 完全不缓存 no-store
public 允许中间代理缓存 public
private 仅浏览器可缓存 private
must-revalidate 过期后必须验证 must-revalidate
immutable 资源不会变化 immutable
stale-while-revalidate=N 允许使用过期缓存同时后台更新 stale-while-revalidate=60

语法与用法

HTTP缓存头设置

代码示例

// 强缓存 - 1小时有效
Cache-Control: max-age=3600, public

// 强缓存 - 1年有效(配合内容哈希)
Cache-Control: max-age=31536000, immutable

// 协商缓存 - ETag
ETag: "abc123"
// 下次请求:If-None-Match: "abc123"

// 协商缓存 - Last-Modified
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
// 下次请求:If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT

// 禁用缓存
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache

前端缓存API

代码示例

// Cache API(Service Worker中使用)
const cache = await caches.open('my-cache-v1');
await cache.put(request, response);
const response = await cache.match(request);
await cache.delete(request);
const keys = await cache.keys();

// 内存缓存(Map实现)
const memoryCache = new Map();
memoryCache.set('key', { data, timestamp });
const cached = memoryCache.get('key');

代码示例

示例1:HTTP缓存头演示

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTTP缓存头演示</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #0f0f23;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #1a1a2e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .cache-flow {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 20px;
            font-family: monospace;
            font-size: 13px;
            line-height: 2;
            overflow-x: auto;
        }
        .flow-browser { color: #48dbfb; }
        .flow-server { color: #2ecc71; }
        .flow-cache { color: #feca57; }
        .flow-header { color: #ff6b6b; }
        .flow-result { color: #a6e3a1; font-weight: bold; }
        .headers-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
            margin: 12px 0;
        }
        .headers-table th, .headers-table td {
            padding: 10px 12px;
            border: 1px solid #2d2d44;
            text-align: left;
        }
        .headers-table th { background: #0f3460; color: #48dbfb; }
        .headers-table td { color: #cdd6f4; font-family: monospace; }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            margin: 4px;
        }
        .btn-primary { background: #48dbfb; color: #0f0f23; }
        .btn-primary:hover { background: #0abde3; }
        .btn-success { background: #2ecc71; color: white; }
        .btn-danger { background: #ff6b6b; color: white; }
        .result-panel {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 16px;
            margin-top: 12px;
            font-family: monospace;
            font-size: 12px;
            max-height: 250px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }
        .scenario-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 12px;
            margin-top: 12px;
        }
        .scenario-item {
            padding: 16px;
            background: #0f3460;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s;
        }
        .scenario-item:hover { transform: translateY(-2px); background: #162d50; }
        .scenario-item h3 { font-size: 14px; color: #48dbfb; margin-bottom: 8px; }
        .scenario-item p { font-size: 12px; color: #888; }
    </style>
</head>
<body>
    <div class="container">
        <h1>HTTP 缓存头演示</h1>

        <!-- 强缓存流程 -->
        <div class="card">
            <h2>强缓存流程</h2>
            <div class="cache-flow">
<span class="flow-browser">浏览器</span>                    <span class="flow-cache">缓存</span>                    <span class="flow-server">服务器</span>
                                                       
     请求资源                                            
   >                          
                                                       
                       检查Cache-Control                 
                       <span class="flow-cache">max-age未过期?</span>              
                                                       
             是                           
                                                      
     <span class="flow-result">200 (from cache)</span>                              
     < 直接返回                           
                                                      
             否                           
                                                       
                               发送请求                  
                             >
                                                       
                               200 + 新资源 + 新缓存头    
                             <
                                                       
     返回新资源                  存入缓存                  
   <                          </div>
        </div>

        <!-- 协商缓存流程 -->
        <div class="card">
            <h2>协商缓存流程</h2>
            <div class="cache-flow">
<span class="flow-browser">浏览器</span>                    <span class="flow-cache">缓存</span>                    <span class="flow-server">服务器</span>
                                                       
     请求资源                                            
   >                          
                                                       
                       检查缓存是否存在                                 
                       <span class="flow-cache">有缓存?</span>                    
                                                       
             否                           
                               发送请求                  
                             >
                               200 + 资源 + <span class="flow-header">ETag</span>/<span class="flow-header">Last-Modified</span>
                             <
                              存入缓存                  
                                                      
             是                           
                                                       
                               发送验证请求               
                               <span class="flow-header">If-None-Match: "abc"</span>     
                               <span class="flow-header">If-Modified-Since: ...</span>    
                             >
                                                       
                                       未修改 
                               <span class="flow-result">304 Not Modified</span>       
                             <
     使用缓存                                            
                                                      
                                      已修改 
                               200 + 新资源              
                             <
     返回新资源                  更新缓存                  </div>
        </div>

        <!-- Cache-Control指令 -->
        <div class="card">
            <h2>Cache-Control 指令详解</h2>
            <table class="headers-table">
                <thead>
                    <tr>
                        <th>指令</th>
                        <th>缓存行为</th>
                        <th>适用场景</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>max-age=31536000</td>
                        <td>缓存1年,期间不发请求</td>
                        <td>带哈希的静态资源</td>
                    </tr>
                    <tr>
                        <td>max-age=0, must-revalidate</td>
                        <td>每次都验证</td>
                        <td>需要最新数据的API</td>
                    </tr>
                    <tr>
                        <td>no-cache</td>
                        <td>可缓存,但每次必须验证</td>
                        <td>HTML文档</td>
                    </tr>
                    <tr>
                        <td>no-store</td>
                        <td>完全不缓存</td>
                        <td>敏感数据</td>
                    </tr>
                    <tr>
                        <td>private</td>
                        <td>仅浏览器缓存,CDN不缓存</td>
                        <td>用户私有数据</td>
                    </tr>
                    <tr>
                        <td>public</td>
                        <td>允许所有缓存</td>
                        <td>公开资源</td>
                    </tr>
                    <tr>
                        <td>immutable</td>
                        <td>资源不会变化,无需验证</td>
                        <td>带内容哈希的URL</td>
                    </tr>
                    <tr>
                        <td>stale-while-revalidate=60</td>
                        <td>允许使用过期缓存60秒</td>
                        <td>非关键API数据</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <!-- 缓存策略场景 -->
        <div class="card">
            <h2>不同资源的缓存策略</h2>
            <div class="scenario-grid">
                <div class="scenario-item">
                    <h3>HTML文档</h3>
                    <p>Cache-Control: no-cache<br>每次验证,确保获取最新版本</p>
                </div>
                <div class="scenario-item">
                    <h3>CSS/JS(带哈希)</h3>
                    <p>Cache-Control: max-age=31536000, immutable<br>文件名含哈希,内容变则URL变</p>
                </div>
                <div class="scenario-item">
                    <h3>图片资源</h3>
                    <p>Cache-Control: max-age=86400<br>缓存1天,适合不常变的图片</p>
                </div>
                <div class="scenario-item">
                    <h3>API数据</h3>
                    <p>Cache-Control: max-age=0, must-revalidate<br>每次验证,保证数据新鲜</p>
                </div>
                <div class="scenario-item">
                    <h3>用户数据</h3>
                    <p>Cache-Control: private, no-cache<br>私有数据,不经过CDN</p>
                </div>
                <div class="scenario-item">
                    <h3>敏感数据</h3>
                    <p>Cache-Control: no-store<br>完全不缓存,如银行信息</p>
                </div>
            </div>
        </div>

        <!-- 实时测试 -->
        <div class="card">
            <h2>缓存行为测试</h2>
            <div style="display:flex; gap:8px; flex-wrap:wrap;">
                <button class="btn btn-primary" onclick="testCache('normal')">普通请求</button>
                <button class="btn btn-success" onclick="testCache('nocache')">no-cache请求</button>
                <button class="btn btn-danger" onclick="testCache('nostore')">no-store请求</button>
                <button class="btn btn-primary" onclick="clearBrowserCache()">清除缓存后请求</button>
            </div>
            <div class="result-panel" id="cacheTestResult">点击按钮测试缓存行为</div>
        </div>
    </div>

    <script>
        async function testCache(mode) {
            const result = document.getElementById('cacheTestResult');
            const url = 'https://httpbin.org/cache/' + (mode === 'nocache' ? '60' : '30');

            result.textContent = `发送${mode}模式请求...\n\n`;

            try {
                const options = { method: 'GET' };

                if (mode === 'nocache') {
                    options.headers = { 'Cache-Control': 'no-cache' };
                } else if (mode === 'nostore') {
                    options.headers = { 'Cache-Control': 'no-store' };
                }

                const startTime = performance.now();
                const response = await fetch(url, options);
                const elapsed = (performance.now() - startTime).toFixed(2);

                result.textContent += `状态码: ${response.status}\n`;
                result.textContent += `耗时: ${elapsed}ms\n`;
                result.textContent += `来源: ${response.type}\n\n`;

                result.textContent += '响应头:\n';
                response.headers.forEach((value, key) => {
                    if (key.startsWith('cache') || key.startsWith('etag') ||
                        key.startsWith('last-') || key.startsWith('expires') ||
                        key.startsWith('pragma') || key.startsWith('content-')) {
                        result.textContent += `  ${key}: ${value}\n`;
                    }
                });

                const data = await response.json();
                result.textContent += '\n响应数据:\n' + JSON.stringify(data, null, 2);

            } catch (error) {
                result.textContent += `请求失败: ${error.message}`;
            }
        }

        async function clearBrowserCache() {
            // 注意:前端无法直接清除浏览器HTTP缓存
            // 可以通过添加随机参数绕过缓存
            const result = document.getElementById('cacheTestResult');
            const url = 'https://httpbin.org/cache/30?_t=' + Date.now();

            result.textContent = '添加时间戳参数绕过缓存...\n\n';

            try {
                const startTime = performance.now();
                const response = await fetch(url);
                const elapsed = (performance.now() - startTime).toFixed(2);

                result.textContent += `状态码: ${response.status}\n`;
                result.textContent += `耗时: ${elapsed}ms\n`;
                result.textContent += `\n提示:添加 ?_t=${Date.now()} 参数可以绕过浏览器缓存\n`;
                result.textContent += `但每次都会下载完整资源,不适合生产环境\n`;
                result.textContent += `更好的方案是使用内容哈希(如 app.abc123.js)`;

            } catch (error) {
                result.textContent += `请求失败: ${error.message}`;
            }
        }
    </script>
</body>
</html>

示例2:前端内存缓存

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>前端内存缓存</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #f5f7fa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; }
        .card {
            background: white;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        }
        .card h2 { font-size: 16px; color: #2c3e50; margin-bottom: 16px; }
        .code-block {
            background: #2c3e50;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .demo-area {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 20px;
            margin-top: 16px;
        }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            margin: 4px;
        }
        .btn-primary { background: #3498db; color: white; }
        .btn-success { background: #2ecc71; color: white; }
        .btn-danger { background: #e74c3c; color: white; }
        .btn-warning { background: #f39c12; color: white; }
        .cache-stats {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 12px;
            margin-bottom: 16px;
        }
        .stat-box {
            background: #f0f0f0;
            border-radius: 8px;
            padding: 16px;
            text-align: center;
        }
        .stat-value {
            font-size: 24px;
            font-weight: bold;
            color: #2c3e50;
        }
        .stat-label {
            font-size: 12px;
            color: #888;
            margin-top: 4px;
        }
        .log-area {
            background: #2c3e50;
            border-radius: 8px;
            padding: 16px;
            font-family: monospace;
            font-size: 12px;
            color: #2ecc71;
            max-height: 250px;
            overflow-y: auto;
            white-space: pre-wrap;
        }
        .log-hit { color: #2ecc71; }
        .log-miss { color: #e74c3c; }
        .log-info { color: #3498db; }
    </style>
</head>
<body>
    <div class="container">
        <h1>前端内存缓存</h1>

        <!-- 缓存类实现 -->
        <div class="card">
            <h2>内存缓存类(支持TTL和LRU)</h2>
            <div class="code-block">/**
 * 内存缓存类
 * 支持TTL过期、LRU淘汰、容量限制
 */
class MemoryCache {
    constructor(options = {}) {
        this.maxSize = options.maxSize || 100;     // 最大缓存条数
        this.defaultTTL = options.defaultTTL || 60000; // 默认TTL 60秒
        this.cache = new Map();  // Map保持插入顺序,适合LRU
        this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
    }

    // 设置缓存
    set(key, value, ttl = this.defaultTTL) {
        // 如果已存在,先删除(更新顺序)
        if (this.cache.has(key)) {
            this.cache.delete(key);
        }

        // 超过容量,删除最旧的(LRU)
        if (this.cache.size >= this.maxSize) {
            const oldestKey = this.cache.keys().next().value;
            this.cache.delete(oldestKey);
        }

        this.cache.set(key, {
            value,
            expiry: ttl > 0 ? Date.now() + ttl : Infinity,
            createdAt: Date.now()
        });
        this.stats.sets++;
    }

    // 获取缓存
    get(key) {
        if (!this.cache.has(key)) {
            this.stats.misses++;
            return null;
        }

        const entry = this.cache.get(key);

        // 检查是否过期
        if (Date.now() > entry.expiry) {
            this.cache.delete(key);
            this.stats.misses++;
            return null;
        }

        // LRU:移到末尾(最近使用)
        this.cache.delete(key);
        this.cache.set(key, entry);
        this.stats.hits++;
        return entry.value;
    }

    // 检查是否存在
    has(key) {
        return this.get(key) !== null;
    }

    // 删除缓存
    delete(key) {
        const deleted = this.cache.delete(key);
        if (deleted) this.stats.deletes++;
        return deleted;
    }

    // 清空缓存
    clear() {
        this.cache.clear();
    }

    // 获取缓存信息
    getInfo(key) {
        const entry = this.cache.get(key);
        if (!entry) return null;
        return {
            remainingTTL: Math.max(0, entry.expiry - Date.now()),
            age: Date.now() - entry.createdAt
        };
    }

    // 命中率
    get hitRate() {
        const total = this.stats.hits + this.stats.misses;
        return total === 0 ? 0 : (this.stats.hits / total * 100).toFixed(1);
    }

    // 缓存大小
    get size() {
        return this.cache.size;
    }
}

/**
 * 带缓存的请求函数
 */
async function cachedFetch(url, options = {}) {
    const cacheKey = `${options.method || 'GET'}:${url}`;
    const ttl = options.cacheTTL || 60000;
    const useCache = options.useCache !== false;

    // 检查缓存
    if (useCache) {
        const cached = requestCache.get(cacheKey);
        if (cached) {
            console.log(`[Cache HIT] ${cacheKey}`);
            return cached;
        }
    }

    console.log(`[Cache MISS] ${cacheKey}`);

    // 发送请求
    const response = await fetch(url, options);
    const data = await response.json();

    // 存入缓存
    if (useCache && response.ok) {
        requestCache.set(cacheKey, data, ttl);
    }

    return data;
}

const requestCache = new MemoryCache({ maxSize: 50, defaultTTL: 30000 });</div>
        </div>

        <!-- 交互式演示 -->
        <div class="card">
            <h2>缓存交互演示</h2>
            <div class="demo-area">
                <div class="cache-stats">
                    <div class="stat-box">
                        <div class="stat-value" id="statHits">0</div>
                        <div class="stat-label">缓存命中</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-value" id="statMisses">0</div>
                        <div class="stat-label">缓存未命中</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-value" id="statHitRate">0%</div>
                        <div class="stat-label">命中率</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-value" id="statSize">0</div>
                        <div class="stat-label">缓存条目</div>
                    </div>
                </div>

                <div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px;">
                    <button class="btn btn-primary" onclick="demoFetch('/users')">请求 /users</button>
                    <button class="btn btn-primary" onclick="demoFetch('/posts')">请求 /posts</button>
                    <button class="btn btn-primary" onclick="demoFetch('/comments')">请求 /comments</button>
                    <button class="btn btn-success" onclick="demoFetch('/users')">再次请求 /users</button>
                    <button class="btn btn-warning" onclick="demoInvalidate('/users')">失效 /users</button>
                    <button class="btn btn-danger" onclick="demoClear()">清空缓存</button>
                </div>

                <div class="log-area" id="demoLog">等待操作...</div>
            </div>
        </div>
    </div>

    <script>
        // 缓存实例
        const demoCache = new MemoryCache({ maxSize: 50, defaultTTL: 30000 });

        // MemoryCache类定义(与上面代码相同)
        function MemoryCache(options = {}) {
            this.maxSize = options.maxSize || 100;
            this.defaultTTL = options.defaultTTL || 60000;
            this.cache = new Map();
            this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
        }

        MemoryCache.prototype.set = function(key, value, ttl) {
            ttl = ttl || this.defaultTTL;
            if (this.cache.has(key)) this.cache.delete(key);
            if (this.cache.size >= this.maxSize) {
                const oldestKey = this.cache.keys().next().value;
                this.cache.delete(oldestKey);
            }
            this.cache.set(key, { value, expiry: Date.now() + ttl, createdAt: Date.now() });
            this.stats.sets++;
        };

        MemoryCache.prototype.get = function(key) {
            if (!this.cache.has(key)) { this.stats.misses++; return null; }
            const entry = this.cache.get(key);
            if (Date.now() > entry.expiry) { this.cache.delete(key); this.stats.misses++; return null; }
            this.cache.delete(key);
            this.cache.set(key, entry);
            this.stats.hits++;
            return entry.value;
        };

        MemoryCache.prototype.delete = function(key) {
            return this.cache.delete(key);
        };

        MemoryCache.prototype.clear = function() { this.cache.clear(); };

        Object.defineProperty(MemoryCache.prototype, 'hitRate', {
            get: function() {
                const total = this.stats.hits + this.stats.misses;
                return total === 0 ? 0 : (this.stats.hits / total * 100).toFixed(1);
            }
        });

        Object.defineProperty(MemoryCache.prototype, 'size', {
            get: function() { return this.cache.size; }
        });

        // 模拟数据
        const mockData = {
            '/users': [{ id: 1, name: '张三' }, { id: 2, name: '李四' }],
            '/posts': [{ id: 1, title: '文章1' }, { id: 2, title: '文章2' }],
            '/comments': [{ id: 1, text: '评论1' }, { id: 2, text: '评论2' }]
        };

        async function demoFetch(path) {
            const log = document.getElementById('demoLog');
            const time = new Date().toLocaleTimeString();

            // 检查缓存
            const cached = demoCache.get(path);
            if (cached) {
                log.textContent += `[${time}] <span class="log-hit">CACHE HIT</span> ${path} → 返回缓存数据 (${Date.now() - cached._cachedAt}ms前缓存)\n`;
            } else {
                log.textContent += `[${time}] <span class="log-miss">CACHE MISS</span> ${path} → 发送网络请求...\n`;

                // 模拟网络延迟
                await new Promise(r => setTimeout(r, 300 + Math.random() * 500));

                const data = { ...mockData[path], _cachedAt: Date.now() };
                demoCache.set(path, data, 30000);
                log.textContent += `[${time}] <span class="log-info">CACHED</span> ${path} → 数据已缓存 (TTL: 30s)\n`;
            }

            updateStats();
            log.scrollTop = log.scrollHeight;
        }

        function demoInvalidate(path) {
            demoCache.delete(path);
            const log = document.getElementById('demoLog');
            log.textContent += `[${new Date().toLocaleTimeString()}] <span class="log-miss">INVALIDATED</span> ${path} → 缓存已失效\n`;
            updateStats();
        }

        function demoClear() {
            demoCache.clear();
            const log = document.getElementById('demoLog');
            log.textContent += `[${new Date().toLocaleTimeString()}] <span class="log-miss">CLEARED</span> 所有缓存已清空\n`;
            updateStats();
        }

        function updateStats() {
            document.getElementById('statHits').textContent = demoCache.stats.hits;
            document.getElementById('statMisses').textContent = demoCache.stats.misses;
            document.getElementById('statHitRate').textContent = demoCache.hitRate + '%';
            document.getElementById('statSize').textContent = demoCache.size;
        }
    </script>
</body>
</html>

示例3:Service Worker缓存策略

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Service Worker缓存策略</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #1a1a2e;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #16213e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .strategy-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 12px;
        }
        .strategy-card {
            background: #0f3460;
            border-radius: 10px;
            padding: 20px;
            border: 1px solid #2d2d44;
        }
        .strategy-card h3 {
            font-size: 15px;
            margin-bottom: 8px;
        }
        .strategy-card .desc {
            font-size: 13px;
            color: #a6adc8;
            line-height: 1.6;
            margin-bottom: 12px;
        }
        .strategy-flow {
            background: #0a0a1a;
            border-radius: 6px;
            padding: 12px;
            font-family: monospace;
            font-size: 11px;
            line-height: 1.6;
        }
        .code-block {
            background: #0a0a1a;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .use-case {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 11px;
            margin-top: 8px;
        }
        .uc-static { background: #2ecc7133; color: #2ecc71; }
        .uc-dynamic { background: #3498db33; color: #3498db; }
        .uc-api { background: #f39c1233; color: #f39c12; }
        .uc-offline { background: #9b59b633; color: #9b59b6; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Service Worker 缓存策略</h1>

        <!-- 六种策略 -->
        <div class="card">
            <h2>六种缓存策略</h2>
            <div class="strategy-grid">
                <div class="strategy-card">
                    <h3 style="color:#2ecc71;">1. Cache First</h3>
                    <div class="desc">优先从缓存获取,缓存未命中时从网络获取并更新缓存。</div>
                    <div class="strategy-flow">
请求 → 查缓存 → 命中 → 返回缓存
              → 未命中 → 网络请求 → 存缓存 → 返回</div>
                    <span class="use-case uc-static">静态资源</span>
                    <span class="use-case uc-offline">离线优先</span>
                </div>

                <div class="strategy-card">
                    <h3 style="color:#3498db;">2. Network First</h3>
                    <div class="desc">优先从网络获取,网络失败时回退到缓存。</div>
                    <div class="strategy-flow">
请求 → 网络请求 → 成功 → 存缓存 → 返回
              → 失败 → 查缓存 → 返回缓存</div>
                    <span class="use-case uc-dynamic">动态内容</span>
                    <span class="use-case uc-api">API请求</span>
                </div>

                <div class="strategy-card">
                    <h3 style="color:#f39c12;">3. Stale While Revalidate</h3>
                    <div class="desc">立即返回缓存(即使过期),同时后台更新缓存。</div>
                    <div class="strategy-flow">
请求 → 查缓存 → 命中 → 立即返回缓存
                     同时 → 网络请求 → 更新缓存</div>
                    <span class="use-case uc-api">非关键API</span>
                    <span class="use-case uc-dynamic">及时性要求不高</span>
                </div>

                <div class="strategy-card">
                    <h3 style="color:#e74c3c;">4. Network Only</h3>
                    <div class="desc">只从网络获取,不使用缓存。</div>
                    <div class="strategy-flow">
请求 → 网络请求 → 成功 → 返回
              → 失败 → 报错</div>
                    <span class="use-case uc-api">实时数据</span>
                    <span class="use-case uc-dynamic">非幂等请求</span>
                </div>

                <div class="strategy-card">
                    <h3 style="color:#9b59b6;">5. Cache Only</h3>
                    <div class="desc">只从缓存获取,不请求网络。</div>
                    <div class="strategy-flow">
请求 → 查缓存 → 命中 → 返回
              → 未命中 → 报错</div>
                    <span class="use-case uc-static">预缓存资源</span>
                    <span class="use-case uc-offline">离线页面</span>
                </div>

                <div class="strategy-card">
                    <h3 style="color:#1abc9c;">6. Cache + Network Update</h3>
                    <div class="desc">先返回缓存,网络响应后更新页面内容。</div>
                    <div class="strategy-flow">
请求 → 查缓存 → 命中 → 返回缓存数据
       → 网络请求 → 成功 → 更新缓存 → 更新页面</div>
                    <span class="use-case uc-api">列表数据</span>
                    <span class="use-case uc-dynamic">快速响应+最新数据</span>
                </div>
            </div>
        </div>

        <!-- Service Worker代码 -->
        <div class="card">
            <h2>Service Worker 缓存实现</h2>
            <div class="code-block">// sw.js - Service Worker文件

const CACHE_NAME = 'app-cache-v1';
const STATIC_ASSETS = [
    '/',
    '/index.html',
    '/styles.css',
    '/app.js',
    '/offline.html'
];

// 安装事件 - 预缓存静态资源
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(STATIC_ASSETS))
            .then(() => self.skipWaiting())
    );
});

// 激活事件 - 清理旧缓存
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then(keys =>
            Promise.all(
                keys.filter(key => key !== CACHE_NAME)
                    .map(key => caches.delete(key))
            )
        ).then(() => self.clients.claim())
    );
});

// 请求拦截 - 根据URL选择不同策略
self.addEventListener('fetch', (event) => {
    const { request } = event;
    const url = new URL(request.url);

    // 非GET请求:Network Only
    if (request.method !== 'GET') return;

    // API请求:Network First
    if (url.pathname.startsWith('/api/')) {
        event.respondWith(networkFirst(request));
        return;
    }

    // 静态资源:Cache First
    if (isStaticAsset(url.pathname)) {
        event.respondWith(cacheFirst(request));
        return;
    }

    // HTML页面:Network First
    if (request.headers.get('accept').includes('text/html')) {
        event.respondWith(networkFirst(request));
        return;
    }

    // 其他:Stale While Revalidate
    event.respondWith(staleWhileRevalidate(request));
});

// === 缓存策略实现 ===

// Cache First
async function cacheFirst(request) {
    const cached = await caches.match(request);
    if (cached) return cached;

    const response = await fetch(request);
    if (response.ok) {
        const cache = await caches.open(CACHE_NAME);
        cache.put(request, response.clone());
    }
    return response;
}

// Network First
async function networkFirst(request) {
    try {
        const response = await fetch(request);
        if (response.ok) {
            const cache = await caches.open(CACHE_NAME);
            cache.put(request, response.clone());
        }
        return response;
    } catch (error) {
        const cached = await caches.match(request);
        if (cached) return cached;
        // 返回离线页面
        if (request.headers.get('accept').includes('text/html')) {
            return caches.match('/offline.html');
        }
        throw error;
    }
}

// Stale While Revalidate
async function staleWhileRevalidate(request) {
    const cache = await caches.open(CACHE_NAME);
    const cached = await cache.match(request);

    // 后台更新
    const fetchPromise = fetch(request).then(response => {
        if (response.ok) {
            cache.put(request, response.clone());
        }
        return response;
    });

    // 如果有缓存,立即返回;否则等待网络
    return cached || fetchPromise;
}

function isStaticAsset(pathname) {
    return /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$/.test(pathname);
}</div>
        </div>

        <!-- 注册Service Worker -->
        <div class="card">
            <h2>注册 Service Worker</h2>
            <div class="code-block">// 在主页面中注册Service Worker
if ('serviceWorker' in navigator) {
    window.addEventListener('load', async () => {
        try {
            const registration = await navigator.serviceWorker.register('/sw.js', {
                scope: '/'
            });

            console.log('SW注册成功:', registration.scope);

            // 监听更新
            registration.addEventListener('updatefound', () => {
                const newWorker = registration.installing;
                newWorker.addEventListener('statechange', () => {
                    if (newWorker.state === 'activated') {
                        // 新版本已激活,提示用户刷新
                        if (navigator.serviceWorker.controller) {
                            showUpdateNotification();
                        }
                    }
                });
            });
        } catch (error) {
            console.error('SW注册失败:', error);
        }
    });
}

// Cache API使用示例
async function cacheApiExample() {
    // 打开缓存
    const cache = await caches.open('api-cache-v1');

    // 添加请求到缓存
    await cache.add('/api/users');

    // 手动添加请求和响应
    const response = new Response(JSON.stringify({ hello: 'world' }), {
        headers: { 'Content-Type': 'application/json' }
    });
    await cache.put('/api/hello', response);

    // 匹配缓存
    const cachedResponse = await cache.match('/api/hello');
    const data = await cachedResponse.json();

    // 删除缓存
    await cache.delete('/api/hello');

    // 查看所有缓存的键
    const keys = await cache.keys();
    console.log('缓存键:', keys);
}</div>
        </div>
    </div>
</body>
</html>

示例4:缓存更新与版本管理

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>缓存更新与版本管理</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #fafafa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; }
        .card {
            background: white;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        }
        .card h2 { font-size: 16px; color: #2c3e50; margin-bottom: 16px; }
        .code-block {
            background: #2c3e50;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .version-timeline {
            position: relative;
            padding-left: 30px;
            margin: 20px 0;
        }
        .version-timeline::before {
            content: '';
            position: absolute;
            left: 10px;
            top: 0;
            bottom: 0;
            width: 2px;
            background: #e0e0e0;
        }
        .version-item {
            position: relative;
            padding: 12px 16px;
            background: #f8f9fa;
            border-radius: 8px;
            margin-bottom: 12px;
        }
        .version-item::before {
            content: '';
            position: absolute;
            left: -24px;
            top: 16px;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #3498db;
            border: 2px solid white;
        }
        .version-item.current::before { background: #2ecc71; }
        .version-item.old::before { background: #e0e0e0; }
        .version-tag {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
        }
        .tag-current { background: #d4edda; color: #155724; }
        .tag-old { background: #e2e3e5; color: #6c757d; }
        .tag-pending { background: #fff3cd; color: #856404; }
        p { font-size: 14px; color: #666; line-height: 1.8; margin-bottom: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>缓存更新与版本管理</h1>

        <!-- 缓存版本策略 -->
        <div class="card">
            <h2>缓存版本管理策略</h2>
            <p>
                当应用更新时,需要确保用户获取最新版本的资源。以下是几种常见的缓存更新策略:
            </p>

            <h3 style="font-size:14px; color:#2c3e50; margin:16px 0 8px;">1. 内容哈希(推荐)</h3>
            <div class="code-block">// 构建工具自动在文件名中添加内容哈希
// app.abc123.js → 内容变化时哈希变化 → URL变化 → 绕过缓存

// webpack配置
module.exports = {
    output: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js'
    },
    // CSS文件也加哈希
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash:8].css'
        })
    ]
};

// Vite默认使用内容哈希
// dist/assets/index-abc123.js
// dist/assets/index-def456.css

// HTML引用带哈希的资源
// <script src="/assets/app.abc123.js"></script>
// 当app.js内容变化时,哈希变化,URL变化,浏览器请求新文件</div>

            <h3 style="font-size:14px; color:#2c3e50; margin:16px 0 8px;">2. Service Worker版本更新</h3>
            <div class="code-block">// sw.js
const CACHE_NAME = 'app-v2';  // 修改版本号触发更新

self.addEventListener('install', (event) => {
    // 新SW安装,预缓存新资源
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(STATIC_ASSETS))
    );
});

self.addEventListener('activate', (event) => {
    // 新SW激活,删除旧缓存
    event.waitUntil(
        caches.keys().then(keys =>
            Promise.all(
                keys.filter(key => key !== CACHE_NAME)
                    .map(key => {
                        console.log('删除旧缓存:', key);
                        return caches.delete(key);
                    })
            )
        )
    );
});

// 版本更新时间线:
// v1安装 → v1激活 → 缓存app-v1
// 修改CACHE_NAME → v2安装 → v2激活 → 删除app-v1 → 缓存app-v2</div>

            <h3 style="font-size:14px; color:#2c3e50; margin:16px 0 8px;">3. HTML缓存策略</h3>
            <div class="code-block">// HTML文件必须使用no-cache策略
// 因为HTML引用了带哈希的JS/CSS,如果HTML被缓存,
// 用户将无法获取新版本的JS/CSS引用

// Nginx配置
location / {
    # HTML文件不缓存
    if ($request_uri ~* \.html$) {
        add_header Cache-Control "no-cache, no-store, must-revalidate";
    }

    # 带哈希的静态资源长期缓存
    if ($request_uri ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|svg|woff2)$) {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

// Express配置
app.get('*', (req, res) => {
    if (req.path.endsWith('.html') || req.path === '/') {
        res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
    } else if (/\.[a-f0-9]{8,}\.(js|css)/.test(req.path)) {
        res.set('Cache-Control', 'public, max-age=31536000, immutable');
    }
    res.sendFile(path.join(__dirname, 'dist', req.path));
});</div>
        </div>

        <!-- 缓存更新流程 -->
        <div class="card">
            <h2>应用更新流程</h2>
            <div class="version-timeline">
                <div class="version-item">
                    <span class="version-tag tag-old">v1.0.0</span>
                    <p style="margin-top:4px;">用户首次访问,下载所有资源,缓存 app-v1</p>
                </div>
                <div class="version-item">
                    <span class="version-tag tag-pending">部署v2</span>
                    <p style="margin-top:4px;">开发者部署新版本,修改CACHE_NAME为app-v2</p>
                </div>
                <div class="version-item">
                    <span class="version-tag tag-pending">SW更新</span>
                    <p style="margin-top:4px;">用户访问页面,浏览器检测到新SW,后台安装app-v2缓存</p>
                </div>
                <div class="version-item">
                    <span class="version-tag tag-pending">等待激活</span>
                    <p style="margin-top:4px;">新SW进入waiting状态,旧页面仍使用app-v1缓存</p>
                </div>
                <div class="version-item current">
                    <span class="version-tag tag-current">v2激活</span>
                    <p style="margin-top:4px;">用户关闭所有旧标签页后重新打开,新SW激活,删除app-v1,使用app-v2</p>
                </div>
            </div>
        </div>

        <!-- 缓存失效方案 -->
        <div class="card">
            <h2>主动缓存失效方案</h2>
            <div class="code-block">// 方案1:版本号查询
async function checkForUpdates() {
    const response = await fetch('/api/version?t=' + Date.now());
    const { version } = await response.json();
    const currentVersion = localStorage.getItem('app_version');

    if (currentVersion && currentVersion !== version) {
        // 版本不同,提示用户刷新
        showUpdateBanner();
    }
    localStorage.setItem('app_version', version);
}

// 方案2:Service Worker消息通知
// sw.js
self.addEventListener('message', (event) => {
    if (event.data.type === 'SKIP_WAITING') {
        self.skipWaiting();  // 立即激活新SW
    }
});

// 主页面
navigator.serviceWorker.addEventListener('controllerchange', () => {
    // 新SW已接管,刷新页面
    window.location.reload();
});

// 用户点击更新按钮
async function forceUpdate() {
    const registration = await navigator.serviceWorker.getRegistration();
    if (registration && registration.waiting) {
        registration.waiting.postMessage({ type: 'SKIP_WAITING' });
    }
}

// 方案3:定时轮询检查
setInterval(checkForUpdates, 5 * 60 * 1000); // 每5分钟检查一次

// 方案4:WebSocket推送更新通知
const ws = new WebSocket('wss://example.com/updates');
ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'NEW_VERSION') {
        showUpdateNotification(data.version);
    }
};</div>
        </div>
    </div>
</body>
</html>

浏览器兼容性

特性 Chrome Firefox Safari Edge IE
HTTP缓存 全部 全部 全部 全部 全部
Cache-Control 全部 全部 全部 全部 全部
ETag 全部 全部 全部 全部 全部
Service Worker 40+ 44+ 11.1+ 17+ 不支持
Cache API 40+ 39+ 11.1+ 17+ 不支持
immutable 49+ 不支持 11+ 79+ 不支持
stale-while-revalidate 75+ 不支持 不支持 75+ 不支持

注意事项与最佳实践

1. 缓存策略选择

代码示例

// 不同类型资源的推荐缓存策略

// 静态资源(带内容哈希)
// Cache-Control: max-age=31536000, immutable
// 原因:文件名含哈希,内容变则URL变

// HTML文档
// Cache-Control: no-cache
// 原因:需要每次验证,确保引用最新的JS/CSS

// API数据
// Cache-Control: no-cache 或 max-age=0, must-revalidate
// 原因:需要最新数据

// 图片/字体
// Cache-Control: max-age=86400 (1天)
// 原因:不常变化,但可能更新

// 用户私有数据
// Cache-Control: private, no-cache
// 原因:不应被CDN缓存,需每次验证

2. 避免缓存陷阱

代码示例

// 陷阱1:缓存了过期的API数据
// 解决:设置合理的TTL,关键数据使用no-cache

// 陷阱2:HTML被强缓存导致无法更新
// 解决:HTML必须使用no-cache

// 陷阱3:Service Worker缓存了错误响应
// 解决:只缓存成功的响应
async function cacheFirstSafe(request) {
    const cached = await caches.match(request);
    if (cached) return cached;

    const response = await fetch(request);
    // 只缓存成功响应
    if (response.ok) {
        const cache = await caches.open(CACHE_NAME);
        cache.put(request, response.clone());
    }
    return response;
}

// 陷阱4:POST请求被缓存
// 解决:只缓存GET请求
if (request.method !== 'GET') return;

3. 缓存调试

代码示例

// Chrome DevTools缓存调试
// 1. Network面板 - 查看缓存状态
//    - from disk cache / from memory cache
//    - 304 Not Modified
// 2. Application面板 - Cache Storage
//    - 查看Service Worker缓存内容
// 3. 勾选"Disable cache" - 开发时禁用缓存

// 编程方式查看缓存
async function inspectCache() {
    const cacheNames = await caches.keys();
    for (const name of cacheNames) {
        const cache = await caches.open(name);
        const keys = await cache.keys();
        console.log(`缓存: ${name} (${keys.length} 条)`);
        for (const key of keys) {
            const response = await cache.match(key);
            console.log(`  ${key.url} - ${response.headers.get('date')}`);
        }
    }
}

代码规范示例

规范的缓存配置

代码示例

/**
 * 缓存配置管理
 * 统一管理HTTP缓存头和Service Worker缓存策略
 */
const CacheConfig = {
    // HTTP缓存头配置
    headers: {
        // HTML文档 - 每次验证
        html: {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        },
        // 带哈希的静态资源 - 长期缓存
        hashedAssets: {
            'Cache-Control': 'public, max-age=31536000, immutable'
        },
        // 不带哈希的静态资源 - 短期缓存
        unhashedAssets: {
            'Cache-Control': 'public, max-age=86400'
        },
        // API响应 - 不缓存或短期缓存
        api: {
            'Cache-Control': 'private, no-cache, must-revalidate'
        },
        // 敏感数据 - 完全不缓存
            'Cache-Control': 'no-store',
            'Pragma': 'no-cache'
        }
    },

    // Service Worker缓存策略配置
    swStrategies: {
        '/': 'networkFirst',           // HTML
        '/api/': 'networkFirst',       // API
        '/assets/': 'cacheFirst',      // 静态资源
        '/images/': 'cacheFirst',      // 图片
        '/fonts/': 'cacheFirst',       // 字体
    },

    // 内存缓存配置
    memory: {
        maxSize: 100,
        defaultTTL: 30000,  // 30秒
        apiTTL: 10000,      // API 10秒
        staticTTL: 300000   // 静态数据 5分钟
    }
};

// 根据请求路径自动选择缓存策略
function getCacheStrategy(pathname) {
    for (const [pattern, strategy] of Object.entries(CacheConfig.swStrategies)) {
        if (pathname.startsWith(pattern)) {
            return strategy;
        }
    }
    return 'staleWhileRevalidate'; // 默认策略
}

常见问题与解决方案

问题1:用户看到旧版本页面

代码示例

// 问题:部署新版本后,部分用户仍看到旧页面

// 解决方案1:HTML使用no-cache
// 确保HTML不被强缓存,每次都验证

// 解决方案2:版本检测+提示更新
let currentVersion = __APP_VERSION__; // 构建时注入

async function checkVersion() {
    const response = await fetch('/version.json?t=' + Date.now());
    const { version } = await response.json();

    if (version !== currentVersion) {
        showUpdateNotification('发现新版本,点击刷新');
    }
}

// 解决方案3:Service Worker skipWaiting
// 新SW安装后立即激活,不等待旧标签页关闭
self.addEventListener('install', (event) => {
    self.skipWaiting(); // 跳过等待
});

问题2:缓存雪崩

代码示例

// 问题:大量缓存同时过期,导致瞬间大量请求打到服务器

// 解决方案:给TTL添加随机偏移
function setCacheWithJitter(key, value, baseTTL) {
    const jitter = Math.random() * baseTTL * 0.1; // 10%的随机偏移
    const ttl = baseTTL + jitter;
    cache.set(key, value, ttl);
}

// 使用示例
setCacheWithJitter('users', data, 60000); // 60秒 ± 6秒

问题3:CDN缓存与浏览器缓存不一致

代码示例

// 问题:CDN缓存了旧版本,浏览器获取到过期数据

// 解决方案1:使用内容哈希URL
// CDN和浏览器都缓存带哈希的资源,内容变化时URL变化

// 解决方案2:CDN缓存清除API
// 部署后调用CDN API清除缓存
async function purgeCDNCache(paths) {
    await fetch('https://api.cdn.com/purge', {
        method: 'POST',
        headers: { 'Authorization': 'Bearer xxx' },
        body: JSON.stringify({ paths })
    });
}

// 解决方案3:使用版本化API路径
// /api/v2/users 而不是 /api/users

总结

请求缓存策略是前端性能优化的核心环节,本教程涵盖了以下内容:

  1. HTTP缓存机制:深入理解强缓存(Cache-Control/max-age)和协商缓存(ETag/Last-Modified)的工作原理、判断流程和配置方法。

  2. Cache-Control指令:掌握max-age、no-cache、no-store、public、private、immutable、stale-while-revalidate等指令的含义和适用场景。

  3. 前端内存缓存:实现支持TTL过期和LRU淘汰的内存缓存类,以及带缓存的请求函数封装。

  4. Service Worker缓存:掌握六种缓存策略(Cache First、Network First、Stale While Revalidate等)的实现和适用场景。

  5. 缓存版本管理:学会使用内容哈希、Service Worker版本更新、HTML no-cache策略确保用户获取最新版本。

  6. 缓存更新与失效:了解主动缓存失效方案,包括版本号查询、SW消息通知、WebSocket推送等。

  7. 最佳实践:不同类型资源的缓存策略选择、缓存陷阱规避、缓存调试方法等生产环境必备知识。

常见问题

什么是18. 请求缓存策略?

18. 请求缓存策略是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习18. 请求缓存策略的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

18. 请求缓存策略有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

18. 请求缓存策略适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

18. 请求缓存策略的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise 响应码 async await 内存管理 场景管理 帧率优化 请求缓存 Fetch API

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:17. 请求认证与授权 - 从入门到实践详解 下一篇: 后端通信:19. 错误处理与重试 - 从入门到实践详解

poll相关推荐