静态资源本地缓存方式

文章类型:Vue

发布者:hp

发布时间:2026-07-09

在Web开发中,合理利用本地缓存可以显著提升页面加载速度、减少服务器压力、节省带宽成本。本文将全面介绍静态资源本地缓存的各种方式和最佳实践。

缓存方式概览

缓存方式存储位置容量限制持久化应用场景
HTTP缓存浏览器缓存依浏览器所有静态资源
Service WorkerCacheStorage依浏览器PWA、离线应用
LocalStorage浏览器本地5-10MB小文件、配置
SessionStorage浏览器本地5-10MB会话期间数据
IndexedDB浏览器本地50MB-数GB大量结构化数据
Memory Cache内存短期缓存
Application Cache浏览器缓存依浏览器已废弃

方法一:HTTP缓存(最常用)


工作原理

通过HTTP响应头控制浏览器缓存行为,是最基础也是最重要的缓存方式。


浏览器请求资源
    ↓
检查本地缓存
    ↓
    ├─ 有缓存 → 检查是否过期
    │            ↓
    │      ├─ 未过期 → 直接使用(from disk cache)
    │      │
    │      └─ 已过期 → 协商缓存验证
    │                  ↓
    │            ├─ 304 Not Modified → 使用缓存
    │            └─ 200 OK → 下载新资源
    │
    └─ 无缓存 → 发起网络请求
                

1. 强缓存(Strong Cache)

Expires(HTTP/1.0)

// 服务器响应头
Expires: Wed, 21 Oct 2026 07:28:00 GMT

// 问题:依赖客户端时间,可能不准确

Cache-Control(HTTP/1.1,推荐)

// 常用指令
Cache-Control: max-age=31536000           // 缓存1年
Cache-Control: max-age=0                  // 立即过期
Cache-Control: no-cache                   // 需要协商缓存
Cache-Control: no-store                   // 完全不缓存
Cache-Control: public                     // 可被任何缓存
Cache-Control: private                    // 仅浏览器缓存
Cache-Control: immutable                  // 内容不可变
Cache-Control: must-revalidate            // 过期必须验证

// 组合使用
Cache-Control: public, max-age=31536000, immutable

2. 协商缓存(Negotiation Cache)

Last-Modified / If-Modified-Since

// 首次请求 - 服务器响应
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Jan 2026 09:00:00 GMT
Cache-Control: no-cache

// 再次请求 - 客户端发送
GET /style.css HTTP/1.1
If-Modified-Since: Tue, 12 Jan 2026 09:00:00 GMT

// 服务器响应
// 未修改:304 Not Modified(不返回内容)
// 已修改:200 OK(返回新内容)

ETag / If-None-Match(更精确)

// 首次请求 - 服务器响应
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: no-cache

// 再次请求 - 客户端发送
GET /script.js HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

// 服务器比对ETag
// 相同:304 Not Modified
// 不同:200 OK(返回新内容)
 ETag vs Last-Modified
  • ETag更精确(基于内容哈希)
  • Last-Modified精度只到秒
  • 文件内容未变但修改时间变了,ETag不变
  • 优先使用ETag

Nginx配置示例

server {
    listen 80;
    server_name example.com;
    
    # HTML文件 - 协商缓存
    location ~* \.html$ {
        add_header Cache-Control "no-cache";
        etag on;
    }
    
    # CSS/JS - 强缓存(带版本号)
    location ~* \.(css|js)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        etag on;
    }
    
    # 图片 - 强缓存
    location ~* \.(jpg|jpeg|png|gif|svg|webp)$ {
        add_header Cache-Control "public, max-age=2592000";  # 30天
    }
    
    # 字体文件 - 强缓存
    location ~* \.(woff|woff2|ttf|eot)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header Access-Control-Allow-Origin "*";
    }
}

Node.js Express配置

const express = require('express');
const app = express();

// 静态资源缓存
app.use('/static', express.static('public', {
    maxAge: '1y',           // 缓存1年
    immutable: true,        // 标记为不可变
    etag: true,             // 启用ETag
    lastModified: true      // 启用Last-Modified
}));

// HTML文件不缓存
app.get('*.html', (req, res) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.sendFile(__dirname + req.path);
});

// API接口不缓存
app.use('/api', (req, res, next) => {
    res.setHeader('Cache-Control', 'no-store');
    next();
});

方法二:Service Worker + Cache API


简介

Service Worker是运行在浏览器后台的脚本,可以拦截网络请求,实现强大的缓存策略,是PWA的核心技术。

优点

  • 完全控制缓存逻辑
  • 支持离线访问
  • 可实现复杂缓存策略
  • 不占用主线程
  • 缺点
  • 需要HTTPS
  • 异步API,学习曲线陡
  • 调试相对复杂

注册Service Worker

// main.js - 注册
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js')
            .then(registration => {
                console.log(' SW注册成功:', registration.scope);
            })
            .catch(error => {
                console.error(' SW注册失败:', error);
            });
    });
}

基础缓存策略

// sw.js - Service Worker文件
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
    '/',
    '/styles/main.css',
    '/script/main.js',
    '/images/logo.png'
];

// 安装事件 - 缓存资源
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                console.log(' 开始缓存文件');
                return cache.addAll(urlsToCache);
            })
    );
});

// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME) {
                        console.log(' 删除旧缓存:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});

// 拦截请求 - 返回缓存
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                // 缓存命中,返回缓存
                if (response) {
                    return response;
                }
                // 缓存未命中,发起网络请求
                return fetch(event.request);
            })
    );
});

高级缓存策略

1. Cache First(缓存优先)

// 适用场景:静态资源,如CSS、JS、图片
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(cached => cached || fetch(event.request))
    );
});

2. Network First(网络优先)

// 适用场景:API接口、实时数据
self.addEventListener('fetch', event => {
    event.respondWith(
        fetch(event.request)
            .then(response => {
                // 请求成功,更新缓存
                const cloned = response.clone();
                caches.open(CACHE_NAME).then(cache => {
                    cache.put(event.request, cloned);
                });
                return response;
            })
            .catch(() => {
                // 网络失败,返回缓存
                return caches.match(event.request);
            })
    );
});

3. Stale While Revalidate(陈旧内容优先+后台更新)

// 适用场景:平衡性能和新鲜度
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.open(CACHE_NAME).then(cache => {
            return cache.match(event.request).then(cached => {
                // 立即返回缓存
                const fetchPromise = fetch(event.request).then(response => {
                    // 后台更新缓存
                    cache.put(event.request, response.clone());
                    return response;
                });
                
                // 返回缓存或网络请求(哪个先完成)
                return cached || fetchPromise;
            });
        })
    );
});

4. Cache Only(仅缓存)

// 适用场景:离线应用的核心资源
self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request));
});

5. Network Only(仅网络)

// 适用场景:不需要缓存的请求
self.addEventListener('fetch', event => {
    event.respondWith(fetch(event.request));
});

完整示例:混合策略

// sw.js - 智能缓存策略
const CACHE_NAME = 'v2';
const STATIC_CACHE = 'static-v2';
const DYNAMIC_CACHE = 'dynamic-v2';

// 静态资源列表
const STATIC_FILES = [
    '/',
    '/index.html',
    '/css/app.css',
    '/js/app.js',
    '/images/logo.png'
];

// 安装
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(STATIC_CACHE)
            .then(cache => cache.addAll(STATIC_FILES))
    );
    self.skipWaiting();  // 立即激活
});

// 激活
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(keys => {
            return Promise.all(
                keys.filter(key => key !== STATIC_CACHE && key !== DYNAMIC_CACHE)
                    .map(key => caches.delete(key))
            );
        })
    );
    self.clients.claim();  // 立即控制页面
});

// 拦截请求 - 智能策略
self.addEventListener('fetch', event => {
    const { request } = event;
    const url = new URL(request.url);
    
    // 静态资源:Cache First
    if (STATIC_FILES.includes(url.pathname)) {
        event.respondWith(cacheFirst(request));
    }
    // API接口:Network First
    else if (url.pathname.startsWith('/api/')) {
        event.respondWith(networkFirst(request));
    }
    // 图片:Stale While Revalidate
    else if (request.destination === 'image') {
        event.respondWith(staleWhileRevalidate(request));
    }
    // 其他:Network Only
    else {
        event.respondWith(fetch(request));
    }
});

// Cache First策略
async function cacheFirst(request) {
    const cached = await caches.match(request);
    return cached || fetch(request);
}

// Network First策略
async function networkFirst(request) {
    const cache = await caches.open(DYNAMIC_CACHE);
    try {
        const response = await fetch(request);
        cache.put(request, response.clone());
        return response;
    } catch (error) {
        return await caches.match(request);
    }
}

// Stale While Revalidate策略
async function staleWhileRevalidate(request) {
    const cache = await caches.open(DYNAMIC_CACHE);
    const cached = await cache.match(request);
    
    const fetchPromise = fetch(request).then(response => {
        cache.put(request, response.clone());
        return response;
    });
    
    return cached || fetchPromise;
}

Workbox - 简化Service Worker开发

// 安装Workbox
npm install workbox-webpack-plugin --save-dev

// webpack.config.js
const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
    plugins: [
        new GenerateSW({
            clientsClaim: true,
            skipWaiting: true,
            runtimeCaching: [
                {
                    urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
                    handler: 'CacheFirst',
                    options: {
                        cacheName: 'images',
                        expiration: {
                            maxEntries: 60,
                            maxAgeSeconds: 30 * 24 * 60 * 60  // 30天
                        }
                    }
                },
                {
                    urlPattern: /^https:\/\/api\.example\.com/,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'api',
                        networkTimeoutSeconds: 3
                    }
                }
            ]
        })
    ]
};

// 或直接在sw.js中使用
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');

workbox.routing.registerRoute(
    ({ request }) => request.destination === 'image',
    new workbox.strategies.CacheFirst()
);

workbox.routing.registerRoute(
    ({ url }) => url.pathname.startsWith('/api/'),
    new workbox.strategies.NetworkFirst()
);

方法三:LocalStorage / SessionStorage


特点对比

特性LocalStorageSessionStorage
生命周期永久(除非手动删除)标签页关闭即清除
作用域同源所有窗口共享仅当前标签页
容量5-10MB5-10MB
存储类型字符串字符串

基础使用

// 存储
localStorage.setItem('key', 'value');
sessionStorage.setItem('key', 'value');

// 读取
const value = localStorage.getItem('key');

// 删除
localStorage.removeItem('key');

// 清空
localStorage.clear();

// 获取键名
const key = localStorage.key(0);

// 存储对象(需JSON序列化)
const user = { name: 'John', age: 30 };
localStorage.setItem('user', JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem('user'));

实战:缓存静态资源

// 缓存工具类
class LocalCache {
    // 设置缓存(带过期时间)
    static set(key, value, expires = 0) {
        const item = {
            value: value,
            timestamp: Date.now(),
            expires: expires  // 毫秒
        };
        localStorage.setItem(key, JSON.stringify(item));
    }
    
    // 获取缓存
    static get(key) {
        const itemStr = localStorage.getItem(key);
        if (!itemStr) return null;
        
        const item = JSON.parse(itemStr);
        
        // 检查是否过期
        if (item.expires && Date.now() - item.timestamp > item.expires) {
            this.remove(key);
            return null;
        }
        
        return item.value;
    }
    
    // 删除缓存
    static remove(key) {
        localStorage.removeItem(key);
    }
    
    // 清空所有缓存
    static clear() {
        localStorage.clear();
    }
}

// 使用示例
// 缓存CSS文本(1天过期)
LocalCache.set('app-css', cssText, 24 * 60 * 60 * 1000);

// 读取缓存
const cachedCSS = LocalCache.get('app-css');
if (cachedCSS) {
    const style = document.createElement('style');
    style.textContent = cachedCSS;
    document.head.appendChild(style);
} else {
    // 加载CSS
    fetch('/app.css').then(res => res.text()).then(css => {
        LocalCache.set('app-css', css, 24 * 60 * 60 * 1000);
    });
}

缓存图片Base64

// 图片缓存工具
async function cacheImage(url, key) {
    // 检查缓存
    const cached = LocalCache.get(key);
    if (cached) {
        return cached;
    }
    
    // 下载并转Base64
    const response = await fetch(url);
    const blob = await response.blob();
    
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => {
            const base64 = reader.result;
            // 缓存7天
            LocalCache.set(key, base64, 7 * 24 * 60 * 60 * 1000);
            resolve(base64);
        };
        reader.onerror = reject;
        reader.readAsDataURL(blob);
    });
}

// 使用
const imgElement = document.getElementById('logo');
cacheImage('/images/logo.png', 'logo-img').then(base64 => {
    imgElement.src = base64;
});
 注意事项
  • 容量限制:通常5-10MB,超出会报错
  • 同步API:大量读写会阻塞主线程
  • 字符串存储:二进制数据需Base64编码,会增加33%体积
  • 隐私模式:部分浏览器禁用或限制
  • 安全性:不要存储敏感信息(XSS可读取)

方法四:IndexedDB


简介

IndexedDB是浏览器提供的NoSQL数据库,支持存储大量结构化数据和文件。

优点

  • 容量大(50MB-数GB)
  • 异步API(不阻塞UI)
  • 支持索引和查询
  • 可存储Blob等二进制数据
  • 支持事务
  • 缺点
  • API复杂
  • 学习曲线陡
  • 不支持跨域

原生API使用

// 打开数据库
const openDB = () => {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open('MyCache', 1);
        
        // 创建对象存储(首次或升级时)
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            if (!db.objectStoreNames.contains('resources')) {
                const store = db.createObjectStore('resources', { keyPath: 'url' });
                store.createIndex('timestamp', 'timestamp', { unique: false });
            }
        };
        
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
};

// 存储资源
async function saveResource(url, data, type) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['resources'], 'readwrite');
        const store = transaction.objectStore('resources');
        
        const item = {
            url: url,
            data: data,
            type: type,
            timestamp: Date.now()
        };
        
        const request = store.put(item);
        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// 读取资源
async function getResource(url) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['resources'], 'readonly');
        const store = transaction.objectStore('resources');
        const request = store.get(url);
        
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// 删除资源
async function deleteResource(url) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['resources'], 'readwrite');
        const store = transaction.objectStore('resources');
        const request = store.delete(url);
        
        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

使用localForage简化(推荐)

// 安装
npm install localforage

// 使用
import localforage from 'localforage';

// 配置
localforage.config({
    driver: localforage.INDEXEDDB,
    name: 'MyApp',
    storeName: 'resources',
    version: 1.0
});

// 存储
await localforage.setItem('logo', imageBlob);
await localforage.setItem('config', { theme: 'dark' });

// 读取
const logo = await localforage.getItem('logo');
const config = await localforage.getItem('config');

// 删除
await localforage.removeItem('logo');

// 清空
await localforage.clear();

// 获取所有键
const keys = await localforage.keys();

// 遍历
await localforage.iterate((value, key) => {
    console.log(key, value);
});

实战:缓存图片和大文件

// 图片缓存管理器
class ImageCacheManager {
    constructor() {
        this.db = localforage.createInstance({
            name: 'ImageCache'
        });
    }
    
    // 缓存图片
    async cacheImage(url) {
        // 检查缓存
        const cached = await this.db.getItem(url);
        if (cached) {
            return URL.createObjectURL(cached.blob);
        }
        
        // 下载图片
        const response = await fetch(url);
        const blob = await response.blob();
        
        // 存储
        await this.db.setItem(url, {
            blob: blob,
            timestamp: Date.now(),
            size: blob.size
        });
        
        return URL.createObjectURL(blob);
    }
    
    // 获取缓存大小
    async getCacheSize() {
        let totalSize = 0;
        await this.db.iterate((value) => {
            totalSize += value.size || 0;
        });
        return totalSize;
    }
    
    // 清理过期缓存(7天)
    async cleanExpired() {
        const expireTime = 7 * 24 * 60 * 60 * 1000;
        const now = Date.now();
        
        const keysToDelete = [];
        await this.db.iterate((value, key) => {
            if (now - value.timestamp > expireTime) {
                keysToDelete.push(key);
            }
        });
        
        for (const key of keysToDelete) {
            await this.db.removeItem(key);
        }
        
        return keysToDelete.length;
    }
    
    // 限制缓存大小(100MB)
    async limitCacheSize(maxSize = 100 * 1024 * 1024) {
        const items = [];
        await this.db.iterate((value, key) => {
            items.push({ key, value });
        });
        
        // 按时间排序(旧的优先删除)
        items.sort((a, b) => a.value.timestamp - b.value.timestamp);
        
        let totalSize = items.reduce((sum, item) => sum + item.value.size, 0);
        
        for (const item of items) {
            if (totalSize <= maxSize) break;
            await this.db.removeItem(item.key);
            totalSize -= item.value.size;
        }
    }
}

// 使用
const imageCache = new ImageCacheManager();

// 显示图片
async function displayImage(imageUrl) {
    const img = document.createElement('img');
    img.src = await imageCache.cacheImage(imageUrl);
    document.body.appendChild(img);
}

// 定期清理
setInterval(async () => {
    const deleted = await imageCache.cleanExpired();
    console.log(`清理了${deleted}个过期缓存`);
    await imageCache.limitCacheSize();
}, 24 * 60 * 60 * 1000);  // 每天执行

方法五:Memory Cache(内存缓存)


简介

浏览器自动管理的内存缓存,速度最快但生命周期短(标签页关闭即清除)。

特点

  • 由浏览器自动管理,开发者无法直接控制
  • 优先级高于Disk Cache
  • 适合重复使用的小资源
  • 容量较小,资源竞争激烈

触发条件

// 内存缓存通常缓存以下资源:
// 1. 页面中已经加载过的图片
// 2. 预加载的资源
<link rel="preload" href="script.js" as="script">

// 3. Base64图片
<img src="data:image/png;base64,...">

// 4. 小文件(通常<200KB)

模拟内存缓存

// 简单的内存缓存实现
class MemoryCache {
    constructor(maxSize = 50 * 1024 * 1024) {  // 50MB
        this.cache = new Map();
        this.maxSize = maxSize;
        this.currentSize = 0;
    }
    
    set(key, value) {
        const size = this.calculateSize(value);
        
        // 容量检查
        while (this.currentSize + size > this.maxSize && this.cache.size > 0) {
            // LRU: 删除最早的项
            const firstKey = this.cache.keys().next().value;
            this.delete(firstKey);
        }
        
        this.cache.set(key, {
            value,
            size,
            timestamp: Date.now()
        });
        this.currentSize += size;
    }
    
    get(key) {
        const item = this.cache.get(key);
        if (!item) return null;
        
        // 更新访问时间(LRU)
        this.cache.delete(key);
        this.cache.set(key, item);
        
        return item.value;
    }
    
    delete(key) {
        const item = this.cache.get(key);
        if (item) {
            this.currentSize -= item.size;
            this.cache.delete(key);
        }
    }
    
    clear() {
        this.cache.clear();
        this.currentSize = 0;
    }
    
    calculateSize(value) {
        if (typeof value === 'string') {
            return value.length * 2;  // UTF-16
        }
        if (value instanceof Blob) {
            return value.size;
        }
        return JSON.stringify(value).length * 2;
    }
}

// 使用
const memCache = new MemoryCache();
memCache.set('user-data', userData);
const cached = memCache.get('user-data');

完整缓存方案设计


多层缓存架构


请求资源流程:

1. Memory Cache(内存)
   ├─ 命中 → 返回(最快)
   └─ 未命中 ↓

2. Service Worker Cache
   ├─ 命中 → 返回(快)
   └─ 未命中 ↓

3. HTTP Cache(Disk Cache)
   ├─ 命中 → 返回(较快)
   └─ 未命中 ↓

4. IndexedDB / LocalStorage
   ├─ 命中 → 返回(中等)
   └─ 未命中 ↓

5. Network(网络请求)
   └─ 下载资源(慢)
                

统一缓存管理器

// 综合缓存管理
class UnifiedCacheManager {
    constructor() {
        this.memoryCache = new Map();
        this.idbCache = localforage.createInstance({
            name: 'UnifiedCache'
        });
    }
    
    // 获取资源(多层查找)
    async get(url, options = {}) {
        const { maxAge = 0 } = options;
        
        // 1. 检查内存缓存
        const memCached = this.memoryCache.get(url);
        if (memCached && this.isValid(memCached, maxAge)) {
            console.log(' Memory Cache命中:', url);
            return memCached.data;
        }
        
        // 2. 检查Service Worker缓存
        if ('caches' in window) {
            const swCached = await caches.match(url);
            if (swCached) {
                console.log(' SW Cache命中:', url);
                const data = await swCached.text();
                this.setMemoryCache(url, data);
                return data;
            }
        }
        
        // 3. 检查IndexedDB
        const idbCached = await this.idbCache.getItem(url);
        if (idbCached && this.isValid(idbCached, maxAge)) {
            console.log(' IndexedDB命中:', url);
            this.setMemoryCache(url, idbCached.data);
            return idbCached.data;
        }
        
        // 4. 网络请求
        console.log(' 网络请求:', url);
        const response = await fetch(url);
        const data = await response.text();
        
        // 缓存到各层
        await this.setAll(url, data);
        
        return data;
    }
    
    // 设置所有层缓存
    async setAll(url, data) {
        // 内存缓存
        this.setMemoryCache(url, data);
        
        // IndexedDB缓存
        await this.idbCache.setItem(url, {
            data,
            timestamp: Date.now()
        });
        
        // Service Worker缓存
        if ('caches' in window) {
            const cache = await caches.open('unified-cache-v1');
            await cache.put(url, new Response(data));
        }
    }
    
    // 内存缓存
    setMemoryCache(url, data) {
        this.memoryCache.set(url, {
            data,
            timestamp: Date.now()
        });
    }
    
    // 检查有效性
    isValid(cached, maxAge) {
        if (maxAge === 0) return true;
        return Date.now() - cached.timestamp < maxAge;
    }
    
    // 清除所有缓存
    async clearAll() {
        this.memoryCache.clear();
        await this.idbCache.clear();
        if ('caches' in window) {
            const names = await caches.keys();
            await Promise.all(names.map(name => caches.delete(name)));
        }
    }
}

// 使用
const cacheManager = new UnifiedCacheManager();

// 获取资源(30天有效期)
const css = await cacheManager.get('/app.css', {
    maxAge: 30 * 24 * 60 * 60 * 1000
});

// 应用CSS
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);

缓存策略最佳实践


按资源类型选择策略

资源类型推荐策略Cache-Control原因
HTML文件协商缓存no-cache需要及时更新
CSS/JS(带hash)强缓存max-age=31536000, immutable内容不变
图片强缓存max-age=2592000变化少,30天
字体文件强缓存max-age=31536000基本不变
API接口不缓存no-store实时数据
视频强缓存max-age=604800大文件,7天

版本化静态资源

// Webpack配置 - 文件名hash
module.exports = {
    output: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js'
    }
};

// 生成的文件
main.a1b2c3d4.js
vendor.e5f6g7h8.js

// HTML中引用
<script src="/static/js/main.a1b2c3d4.js"></script>

// 优势:文件内容变化时hash变化,自动更新

缓存更新策略

// 1. 版本号更新
const CACHE_VERSION = 'v2';
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;

// 2. 时间戳更新
<script src="/app.js?t=1672531200000"></script>

// 3. Git commit hash
<link href="/style.css?v=abc123f" rel="stylesheet">

// 4. Service Worker强制更新
self.addEventListener('install', event => {
    self.skipWaiting();  // 跳过等待,立即激活
});

self.addEventListener('activate', event => {
    self.clients.claim();  // 立即控制所有页面
});

常见问题与解决


问题1:缓存更新不及时

现象:更新了文件但用户看到的还是旧版本

解决:

  • 使用文件hash命名:app.abc123.js
  • HTML使用协商缓存:Cache-Control: no-cache
  • Service Worker版本管理

问题2:缓存占用过多空间

解决:

// 定期清理IndexedDB
async function cleanOldCache() {
    const maxAge = 30 * 24 * 60 * 60 * 1000;  // 30天
    const now = Date.now();
    
    await localforage.iterate((value, key) => {
        if (now - value.timestamp > maxAge) {
            localforage.removeItem(key);
        }
    });
}

// Service Worker限制缓存数量
workbox.expiration.ExpirationPlugin({
    maxEntries: 50,
    maxAgeSeconds: 30 * 24 * 60 * 60
});

问题3:跨域资源缓存失败

解决:

// 服务器添加CORS头
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 86400

// fetch请求指定模式
fetch(url, {
    mode: 'cors',
    credentials: 'omit'
});

性能监控


缓存命中率统计

// Performance API监控
const cacheStats = {
    total: 0,
    fromCache: 0,
    fromNetwork: 0
};

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        cacheStats.total++;
        
        if (entry.transferSize === 0) {
            // 来自缓存
            cacheStats.fromCache++;
        } else {
            // 来自网络
            cacheStats.fromNetwork++;
        }
    }
    
    console.log('缓存命中率:', 
        (cacheStats.fromCache / cacheStats.total * 100).toFixed(2) + '%'
    );
});

observer.observe({ entryTypes: ['resource'] });

缓存使用情况

// 查看Storage使用情况
if ('storage' in navigator && 'estimate' in navigator.storage) {
    navigator.storage.estimate().then(estimate => {
        const used = (estimate.usage / 1024 / 1024).toFixed(2);
        const quota = (estimate.quota / 1024 / 1024).toFixed(2);
        const percent = (estimate.usage / estimate.quota * 100).toFixed(2);
        
        console.log(`已使用: ${used}MB / ${quota}MB (${percent}%)`);
    });
}

// IndexedDB大小
async function getIndexedDBSize() {
    let totalSize = 0;
    await localforage.iterate((value) => {
        const size = new Blob([JSON.stringify(value)]).size;
        totalSize += size;
    });
    return (totalSize / 1024 / 1024).toFixed(2) + 'MB';
}

实战案例


案例:SPA应用完整缓存方案

// 1. index.html - 协商缓存
// Nginx配置
location = /index.html {
    add_header Cache-Control "no-cache";
    etag on;
}

// 2. 静态资源 - 强缓存(Webpack打包带hash)
// webpack.config.js
output: {
    filename: 'js/[name].[contenthash:8].js',
    publicPath: '/static/'
}

// Nginx配置
location /static/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

// 3. Service Worker - 离线支持
// sw.js
const CACHE_NAME = 'spa-v1';

// 预缓存核心资源
const PRECACHE_URLS = [
    '/',
    '/static/js/main.abc123.js',
    '/static/css/app.def456.css'
];

self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME).then(cache => {
            return cache.addAll(PRECACHE_URLS);
        })
    );
});

// API接口 - Network First
self.addEventListener('fetch', event => {
    if (event.request.url.includes('/api/')) {
        event.respondWith(
            fetch(event.request)
                .catch(() => caches.match(event.request))
        );
    }
});

// 4. 图片 - IndexedDB缓存
// 使用前面的ImageCacheManager
const imageCache = new ImageCacheManager();

document.querySelectorAll('img[data-src]').forEach(async img => {
    const url = img.dataset.src;
    img.src = await imageCache.cacheImage(url);
});

效果对比

指标优化前优化后提升
首次加载3.2s3.0s-6%
二次加载1.8s0.3s-83%
离线可用-
流量消耗2.5MB0.1MB-96%

总结

  1. HTTP缓存是基础
  2. Service Worker是进阶
  3. IndexedDB处理大文件
  4. LocalStorage处理小数据
  5. 多层缓存协同


评论
0条评论遵守法律,文明用语,共同建设文明评论区

暂无评论,快来发表第一条评论吧~

最近更新

站长推荐