跳转到内容

Cache API

一句话: Cache API(caches 全局对象)是一个持久化的、按来源划分的 Request/Response 对键值存储。Service worker 用它在 install 期间存储资源并在离线时提供服务;与浏览器 HTTP 缓存不同,Cache API 完全可编程——不经你的代码操作,没有任何内容会被存入或清除。

caches 是一个 CacheStorage 实例,在 service worker 和页面中均可使用(Chrome 43 / Firefox 41 起支持)。它管理一组具名 Cache 对象。

// service worker 和页面中均可使用
const cache = await caches.open('my-cache-v1');
const cache = await caches.open('app-shell-v3');

若缓存不存在,caches.open() 会自动创建。缓存名称是任意字符串;使用带版本号的名称(app-shell-v3),便于在 activate 中删除旧缓存。

cache.add(request) — 获取并存储单个资源

Section titled “cache.add(request) — 获取并存储单个资源”
await cache.add('/offline.html'); // 先 fetch,再存储

cache.addAll(requests) — 原子地获取并存储多个资源

Section titled “cache.addAll(requests) — 原子地获取并存储多个资源”
await cache.addAll([
'/',
'/app.js',
'/app.css',
'/offline.html',
]);

addAll 是原子操作:若任意请求失败,所有内容均不会存储。在 install 中配合 event.waitUntil() 使用,可确保预缓存失败时中止安装。

cache.put(request, response) — 存储已有的响应

Section titled “cache.put(request, response) — 存储已有的响应”
const response = await fetch(event.request);
await cache.put(event.request, response.clone());

put 不发起请求——它直接存储你传入的 Response。若既要存储又要返回响应,必须先调用 clone()(响应是单次读取的流)。

cache.match(request, options?) — 查找单个条目

Section titled “cache.match(request, options?) — 查找单个条目”
const cached = await cache.match('/app.js');
if (cached) return cached;

caches.match(request) — 按插入顺序查询所有缓存

Section titled “caches.match(request) — 按插入顺序查询所有缓存”
const cached = await caches.match(event.request);

caches.match() 搜索所有已打开的缓存并返回第一个匹配项。不确定资源在哪个缓存中时很有用。

await cache.match(request, {
ignoreSearch: true, // 忽略查询字符串(?v=1)
ignoreMethod: true, // 将 POST 视为 GET 匹配
ignoreVary: true, // 忽略 Vary 头
});
// 从缓存中删除单个条目
await cache.delete('/old-resource.js');
// 删除整个具名缓存
await caches.delete('app-shell-v2');
// 列出所有缓存名称
const names = await caches.keys();

标准做法是在新版本安装后,在 activate 事件中删除旧缓存:

self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((n) => n !== 'app-shell-v3') // 只保留当前缓存
.map((n) => caches.delete(n))
)
)
);
});

Cache Storage 受来源存储配额约束(与 IndexedDB、OPFS 等存储 API 共享)。当设备存储空间紧张时,浏览器会从最近未使用的来源开始清除数据——除非该来源已申请持久化存储。详见持久化、配额与清除策略

no-cors 方式获取的跨域响应是不透明的(status: 0,body 不可读)。你可以缓存不透明响应,但需注意:

  • 无法检查其状态码以确认是否成功。
  • 可能被计为较大条目(浏览器会在配额计算中填充不透明响应,以防止时序攻击)。
  • 过期的不透明响应与失败的响应无法区分。

缓存中优先使用同源或已启用 CORS 的资源。若必须缓存不透明响应,始终搭配重新验证策略使用。

  • 为缓存名称加版本号(shell-v3 而非 shell),并在 activate 中删除旧版本。
  • install 中使用 cache.addAll() 原子地预缓存应用外壳。
  • 调用 cache.put() 前若还需返回响应,务必先 clone() 一份。
  • 明确知道资源在哪个缓存中时,优先使用 cache.match() 而非 caches.match()——速度更快。
  • 通过 DevTools → 应用 → 存储监控存储用量,防止配额悄悄增长。