乐闻世界logo
搜索文章和话题

Service Worker 中的 Cache Storage API 如何使用?

3月6日 22:58

Cache Storage API 是 Service Worker 的核心 API,用于管理请求/响应对的缓存,实现离线访问和性能优化。

基本概念

Cache Storage API 提供了类似数据库的接口来存储和检索网络请求和响应:

javascript
// Cache Storage 是 caches 全局对象 console.log(caches); // CacheStorage 对象 // 主要方法 caches.open(cacheName) // 打开/创建缓存 caches.match(request) // 跨缓存匹配请求 caches.keys() // 获取所有缓存名称 caches.delete(cacheName) // 删除指定缓存

核心方法详解

1. 打开和创建缓存

javascript
// 打开缓存(如果不存在则创建) async function openCache() { const cache = await caches.open('my-cache-v1'); console.log('缓存已打开:', cache); return cache; } // 使用版本号管理缓存 const CACHE_VERSION = 'v2'; const CACHE_NAME = `app-cache-${CACHE_VERSION}`;

2. 添加资源到缓存

javascript
// 方法1:使用 add() - 获取并缓存单个请求 async function addToCache(url) { const cache = await caches.open('my-cache'); await cache.add(url); // 自动 fetch 并缓存 console.log(`已缓存: ${url}`); } // 方法2:使用 addAll() - 批量缓存 async function addMultipleToCache(urls) { const cache = await caches.open('my-cache'); await cache.addAll(urls); console.log(`已批量缓存 ${urls.length} 个资源`); } // 使用示例 const urlsToCache = [ '/', '/index.html', '/styles.css', '/app.js', '/images/logo.png' ]; addMultipleToCache(urlsToCache); // 方法3:使用 put() - 手动存储请求/响应对 async function putInCache(request, response) { const cache = await caches.open('my-cache'); await cache.put(request, response); } // 示例:缓存自定义响应 const request = new Request('/custom-data'); const response = new Response(JSON.stringify({ data: 'value' }), { headers: { 'Content-Type': 'application/json' } }); putInCache(request, response);

3. 从缓存中检索

javascript
// 方法1:match() - 匹配单个请求 async function getFromCache(url) { const cache = await caches.open('my-cache'); const response = await cache.match(url); if (response) { console.log('缓存命中:', url); return response; } console.log('缓存未命中:', url); return null; } // 方法2:跨所有缓存匹配 caches.match('/api/data').then(response => { if (response) { // 在所有缓存中找到匹配 return response; } }); // 方法3:matchAll() - 获取所有匹配 async function getAllMatches(url) { const cache = await caches.open('my-cache'); const responses = await cache.matchAll(url); return responses; }

4. 删除缓存中的资源

javascript
// 删除特定请求 async function deleteFromCache(url) { const cache = await caches.open('my-cache'); const deleted = await cache.delete(url); console.log(deleted ? '删除成功' : '资源不存在'); } // 删除整个缓存 async function deleteCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(deleted ? '缓存已删除' : '缓存不存在'); } // 清理旧缓存(常用在 activate 事件) async function cleanOldCaches(currentCacheName) { const cacheNames = await caches.keys(); const oldCaches = cacheNames.filter(name => name !== currentCacheName); await Promise.all( oldCaches.map(name => caches.delete(name)) ); console.log(`已清理 ${oldCaches.length} 个旧缓存`); }

5. 查看缓存内容

javascript
// 列出所有缓存 async function listAllCaches() { const cacheNames = await caches.keys(); console.log('所有缓存:', cacheNames); return cacheNames; } // 查看特定缓存中的所有请求 async function listCacheContents(cacheName) { const cache = await caches.open(cacheName); const requests = await cache.keys(); console.log(`缓存 "${cacheName}" 内容:`); requests.forEach(request => { console.log(' -', request.url); }); return requests; }

实际应用场景

场景1:预缓存静态资源

javascript
// sw.js const STATIC_CACHE = 'static-v1'; const STATIC_ASSETS = [ '/', '/index.html', '/styles.css', '/app.js', '/manifest.json', '/icons/icon-192x192.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(STATIC_CACHE) .then(cache => cache.addAll(STATIC_ASSETS)) .then(() => self.skipWaiting()) ); });

场景2:动态缓存运行时资源

javascript
// sw.js const DYNAMIC_CACHE = 'dynamic-v1'; self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(response => { // 返回缓存或请求网络 return response || fetch(event.request).then(fetchResponse => { // 动态缓存新资源 return caches.open(DYNAMIC_CACHE).then(cache => { cache.put(event.request, fetchResponse.clone()); return fetchResponse; }); }); }) ); });

场景3:缓存 API 响应

javascript
// sw.js const API_CACHE = 'api-v1'; const API_MAX_AGE = 5 * 60 * 1000; // 5分钟 self.addEventListener('fetch', event => { if (event.request.url.includes('/api/')) { event.respondWith( caches.open(API_CACHE).then(cache => { return cache.match(event.request).then(cachedResponse => { // 检查缓存是否过期 if (cachedResponse) { const cachedTime = new Date( cachedResponse.headers.get('sw-cached-time') ).getTime(); if (Date.now() - cachedTime < API_MAX_AGE) { return cachedResponse; } } // 请求网络并缓存 return fetch(event.request).then(networkResponse => { const clonedResponse = networkResponse.clone(); // 添加自定义缓存时间头 const headers = new Headers(clonedResponse.headers); headers.set('sw-cached-time', new Date().toISOString()); const modifiedResponse = new Response( clonedResponse.body, { ...clonedResponse, headers } ); cache.put(event.request, modifiedResponse); return networkResponse; }); }); }) ); } });

场景4:缓存清理策略

javascript
// sw.js const CACHE_MAX_ITEMS = 50; // 最大缓存数量 async function trimCache(cacheName, maxItems) { const cache = await caches.open(cacheName); const requests = await cache.keys(); if (requests.length > maxItems) { // 删除最旧的缓存 const toDelete = requests.slice(0, requests.length - maxItems); await Promise.all( toDelete.map(request => cache.delete(request)) ); console.log(`已清理 ${toDelete.length} 个旧缓存项`); } } // 定期清理 self.addEventListener('activate', event => { event.waitUntil( trimCache('dynamic-v1', CACHE_MAX_ITEMS) ); });

注意事项

1. Response 只能使用一次

javascript
// ❌ 错误:Response 只能读取一次 const response = await fetch('/api/data'); await cache.put(request, response); // 存储到缓存 return response; // 已经消耗,无法返回 // ✅ 正确:使用 clone() const response = await fetch('/api/data'); await cache.put(request, response.clone()); // 存储副本 return response; // 返回原始响应

2. 存储限制

javascript
// 检查存储配额 async function checkStorageQuota() { if ('storage' in navigator && 'estimate' in navigator.storage) { const estimate = await navigator.storage.estimate(); console.log(`已使用: ${estimate.usage} bytes`); console.log(`配额: ${estimate.quota} bytes`); console.log(`使用率: ${(estimate.usage / estimate.quota * 100).toFixed(2)}%`); } }

3. 缓存键匹配规则

javascript
// URL 必须完全匹配(包括查询参数) await cache.match('/api/data'); // ✅ 匹配 /api/data await cache.match('/api/data?id=1'); // ❌ 不匹配 /api/data // 使用 ignoreSearch 选项忽略查询参数 await cache.match('/api/data?id=1', { ignoreSearch: true // 忽略查询参数 }); // 其他匹配选项 await cache.match(request, { ignoreSearch: true, // 忽略查询参数 ignoreMethod: true, // 忽略 HTTP 方法 ignoreVary: true // 忽略 Vary 头 });

最佳实践

  1. 版本控制:使用版本号管理缓存,便于更新
  2. 定期清理:在 activate 事件中清理过期缓存
  3. 存储限制:注意浏览器的存储配额限制
  4. 错误处理:添加适当的错误处理和降级方案
  5. 性能优化:避免缓存过多数据,定期清理
标签:Service Worker