# Cache API

> 如何使用 Cache Storage API 打开、填充、查询和删除缓存——这是供 service worker 和页面使用的、用于存储 Request/Response 对的持久化键值存储。

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

## `caches` 全局对象

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

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

## 打开缓存

```js
const cache = await caches.open('app-shell-v3');
```

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

## 添加资源

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

```js
await cache.add('/offline.html'); // 先 fetch，再存储
```

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

```js
await cache.addAll([
  '/',
  '/app.js',
  '/app.css',
  '/offline.html',
]);
```

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

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

```js
const response = await fetch(event.request);
await cache.put(event.request, response.clone());
```

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

## 查询

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

```js
const cached = await cache.match('/app.js');
if (cached) return cached;
```

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

```js
const cached = await caches.match(event.request);
```

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

### 匹配选项

```js
await cache.match(request, {
  ignoreSearch: true,   // 忽略查询字符串（?v=1）
  ignoreMethod: true,   // 将 POST 视为 GET 匹配
  ignoreVary: true,     // 忽略 Vary 头
});
```

## 删除条目与缓存

```js
// 从缓存中删除单个条目
await cache.delete('/old-resource.js');

// 删除整个具名缓存
await caches.delete('app-shell-v2');

// 列出所有缓存名称
const names = await caches.keys();
```

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

```js
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 共享）。当设备存储空间紧张时，浏览器会从最近未使用的来源开始清除数据——除非该来源已申请持久化存储。详见[持久化、配额与清除策略](/zh/reference/storage/persistence/)。

## 缓存不透明响应

以 `no-cors` 方式获取的跨域响应是*不透明*的（`status: 0`，body 不可读）。你可以缓存不透明响应，但需注意：

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

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

## 实践清单

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

## 相关参考

- [缓存策略](/zh/reference/service-worker/caching-strategies/) — 使用 Cache API 的各种模式
- [Fetch 事件与路由](/zh/reference/service-worker/fetch-event/) — 请求时调用 `caches.match()` 的位置
- [持久化、配额与清除策略](/zh/reference/storage/persistence/) — 存储配额与清除策略