5月28日 01:49

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

Cache Storage API 是 Service Worker 中管理请求/响应对缓存的接口,支持离线访问和性能优化,是前端 PWA 和面试的高频考点。

核心答案

Cache Storage API 做什么? 在 Service Worker 中以代码驱动的方式缓存网络请求和响应,实现对缓存内容的完全控制,替代传统的 HTTP 缓存启发式策略。

关键方法速记:

  • caches.open(name) — 打开/创建命名缓存
  • cache.add(url) / cache.addAll(urls) — fetch 并缓存
  • cache.put(req, res) — 手动存储请求/响应对
  • cache.match(req) / caches.match(req) — 检索缓存(后者跨所有缓存)
  • cache.delete(req) / caches.delete(name) — 删除缓存项或整个缓存
  • caches.keys() — 列出所有缓存名称

与 HTTP 缓存的关系: Cache Storage 是应用层缓存,优先级高于 HTTP 缓存;浏览器填充 Cache Storage 时仍会检查 HTTP 缓存。建议对带版本哈希的资源设置 Cache-Control: max-age=31536000,其余资源配合 Service Worker 手动管理。

基本使用

javascript
// install 阶段预缓存静态资源 const CACHE_NAME = 'app-v1'; const ASSETS = ['/', '/index.html', '/styles.css', '/app.js']; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(ASSETS)) .then(() => self.skipWaiting()) ); }); // activate 阶段清理旧缓存 self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(names => Promise.all( names.filter(n => n !== CACHE_NAME).map(n => caches.delete(n)) ) ) ); });

三种缓存策略

面试中最常问的缓存策略,根据场景选择:

Cache First(缓存优先)

适用场景:静态资源、字体、图片等不常变化的内容。

javascript
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(cached => { return cached || fetch(event.request).then(response => { const clone = response.clone(); caches.open('dynamic-v1').then(cache => cache.put(event.request, clone)); return response; }); }) ); });

Network First(网络优先)

适用场景:API 请求、频繁更新的内容。

javascript
self.addEventListener('fetch', event => { event.respondWith( fetch(event.request).then(response => { const clone = response.clone(); caches.open('api-v1').then(cache => cache.put(event.request, clone)); return response; }).catch(() => caches.match(event.request)) ); });

Stale-While-Revalidate(先用缓存,后台更新)

适用场景:非关键数据,可接受短暂过期。

javascript
self.addEventListener('fetch', event => { event.respondWith( caches.open('swr-v1').then(cache => cache.match(event.request).then(cached => { const fetchPromise = fetch(event.request).then(response => { cache.put(event.request, response.clone()); return response; }); return cached || fetchPromise; }) ) ); });

关键注意事项

Response 只能消费一次

javascript
// 错误:Response body 被消耗后无法再次使用 const res = await fetch('/api/data'); await cache.put(request, res); return res; // 已消耗,返回空 // 正确:clone() 创建副本 const res = await fetch('/api/data'); await cache.put(request, res.clone()); return res;

缓存匹配规则

javascript
// 默认严格匹配 URL(含查询参数) await cache.match('/api/data'); // 不匹配 /api/data?id=1 // 使用选项放宽匹配 await cache.match(request, { ignoreSearch: true, // 忽略查询参数 ignoreMethod: true, // 忽略 HTTP 方法 ignoreVary: true // 忽略 Vary 头 });

存储配额

javascript
if ('storage' in navigator && 'estimate' in navigator.storage) { const { usage, quota } = await navigator.storage.estimate(); console.log(`已用 ${(usage / 1024 / 1024).toFixed(1)}MB,配额 ${(quota / 1024 / 1024).toFixed(0)}MB`); }

超出配额后浏览器会按 LRU 策略清理,建议控制缓存数量上限:

javascript
async function trimCache(name, maxItems) { const cache = await caches.open(name); const keys = await cache.keys(); if (keys.length > maxItems) { await Promise.all(keys.slice(0, keys.length - maxItems).map(k => cache.delete(k))); } }

常见面试追问

Q: Cache Storage 和 localStorage 有什么区别? localStorage 是同步的、容量小(约5MB)、只能存字符串;Cache Storage 是异步的、容量大(通常数百MB)、专门存储 Request/Response 对象,适合缓存网络资源。

Q: 页面主线程能直接用 Cache Storage 吗? 能。caches 对象在 window 和 Service Worker 中都可用,但缓存策略的拦截逻辑必须在 Service Worker 的 fetch 事件中实现。

Q: 如何让用户获取到更新后的缓存? 修改缓存版本号(如 app-v1app-v2),新 Service Worker install 后会在 activate 事件中清理旧缓存。注意页面需要关闭再打开才会激活新 Service Worker,也可用 skipWaiting() + clients.claim() 立即生效。

标签:Service Worker