Cache API 深度指南:PWA 离线缓存与请求策略完全解析

0 阅读9分钟

Cache API 是浏览器提供的一种存储机制,专门用于缓存 Request / Response 对象对。它与 Service Worker 紧密配合,是实现渐进式 Web 应用(PWA)离线可用、性能优化的核心工具。以下从多个维度对 Cache API 进行系统分析。


一、 概述

Cache API 允许开发者精确控制网络资源的缓存策略。与传统的 HTTP 缓存不同,Cache API 是程序化的,开发者可以决定哪些资源被缓存、何时缓存、何时更新,并能在离线状态下直接返回缓存的响应。

注:Cache API 在 window 和 worker 上下文中均可直接使用,无需依赖 Service Worker,但与 Service Worker 的 fetch 事件结合是其主要应用场景。

  • 接口来源caches 全局对象(属于 WindowWorkerGlobalScope)。
  • 存储模型:以 缓存仓库(CacheStorage) 为顶层,内部包含多个命名 缓存对象(Cache),每个缓存对象存储多个键值对,其中键是 Request 对象,值是 Response 对象。
  • 生命周期:由开发者显式管理(添加、删除、匹配),数据可持久化,但浏览器可能在存储压力下自动清除(尤其是未使用 persist 标记时)。

二、 核心特性

1. 存储内容

  • Request 对象(通常使用 URL 字符串作为标识,但也可包含请求方法、头等信息)。
  • Response 对象(支持所有 HTTP 响应特性,如状态码、头信息、body)。
  • 可以存储跨域资源(CORS 响应需设置 crossorigin 属性)。
  • ⚠️ 注意:若跨域请求使用 mode: 'no-cors',得到的响应为 不透明响应(opaque response),其状态码固定为 0,body 不可读,且缓存后无法判断资源是否已更新。应避免将不透明响应用于关键业务逻辑。

2. API 接口

方法说明
caches.open(name)打开(或创建)指定名称的缓存对象,返回 Promise<Cache>
cache.put(request, response)将请求-响应对存入缓存。
cache.add(request) / addAll(requests)自动 fetch 请求并将结果存入缓存。
cache.match(request, options)匹配给定请求的缓存响应。
cache.delete(request, options)删除匹配的缓存条目。
caches.keys()获取所有缓存仓库名称。
caches.delete(name)删除整个缓存仓库。

3. 作用域与权限

  • 可用环境:Service Worker、Window、Worker、SharedWorker。
  • 同源限制:缓存仓库与源(origin)绑定,不同源的页面无法访问彼此的缓存(但 Service Worker 可拦截跨域请求并缓存跨域响应)。
  • 安全上下文:仅在 HTTPS(或 localhost)环境下可用,确保数据在传输和存储过程中的安全性。

4. 容量与持久性

  • 配额:与 IndexedDB 共享存储空间,通常可达到几百 MB 甚至更多,具体取决于浏览器和磁盘可用空间。
  • 持久性:默认情况下,缓存数据可能被浏览器在存储压力下自动清除(如用户清理站点数据,或浏览器自动驱逐旧数据)。可以通过 Storage Manager API 请求持久化存储(navigator.storage.persist()),减少被清除的概率。
  • 开发者可通过 navigator.storage.estimate() 查看当前源下所有存储机制(含 Cache API)的 usage 和 quota,辅助制定清理策略。

三、 与 Service Worker 的协作

Cache API 最常见的使用场景是作为 Service Worker 的缓存引擎。Service Worker 在安装、激活、fetch 事件中操作 Cache API,实现离线优先或网络优先的策略。

典型流程

  1. 安装阶段:预缓存关键静态资源(HTML、CSS、JS、图标)。
    self.addEventListener("install", (event) => {
      event.waitUntil(
        caches
          .open("v1")
          .then((cache) => cache.addAll(["/", "/styles.css", "/app.js"])),
      );
    });
    
  2. 激活阶段:清理旧缓存。
  3. 请求阶段:根据策略决定返回缓存还是网络响应。
    self.addEventListener("fetch", (event) => {
      event.respondWith(
        caches.match(event.request).then((response) => {
          return response || fetch(event.request);
        }),
      );
    });
    

缓存策略示例

  • Cache Only:完全离线。
  • Network Only:仅网络(不缓存)。
  • Cache First (Stale-While-Revalidate):优先返回缓存,后台更新。
  • Network First:优先网络,失败时使用缓存。
  • Cache Then Network:先返回缓存,再请求网络更新 UI。

四、 主要使用场景

场景说明
离线 Web 应用 (PWA)核心存储机制,确保用户在没有网络时仍能访问基本功能和内容。
静态资源缓存缓存 HTML、CSS、JS、图片等,实现秒开和减少服务器负载。
API 响应缓存缓存接口数据,提升二次加载速度,支持弱网环境。
后备内容(Fallback)当网络请求失败时,返回缓存的默认页面或占位图。
版本化管理通过缓存名称(如 v1v2)实现原子化更新,避免新旧资源混用。

五、 安全与限制

1. 安全上下文

  • Cache API 只在 HTTPS(或 localhost)中可用,确保缓存数据不会被中间人篡改。

2. 跨域资源缓存

  • 可以缓存跨域资源(如 CDN 上的字体、图片),但需要这些资源支持 CORS 且请求时携带 mode: 'cors'
  • 缓存的跨域响应可能受 CORS 策略限制(例如无法访问响应头),但仍可作为静态资源使用。

3. 敏感数据

  • 缓存的数据(包括响应正文)以明文形式存储在磁盘上,因此不应缓存密码、Token 等高度敏感信息。
  • 如果必须缓存,应确保内容本身已加密,且密钥不在客户端暴露。

4. 清除策略

  • 用户可以通过浏览器设置清除所有站点数据,包括 Cache API 存储。
  • 浏览器在磁盘空间不足时可能自动清除缓存,尤其是未请求持久化的缓存。
    • Chrome/Edge:采用 LRU 策略,优先清理最久未被访问的缓存条目。
    • Safari:受 ITP 影响,若站点在 7 天内无用户交互,所有脚本可写存储(含 Cache API)将被清除。
  • 开发者可通过 caches.delete() 主动管理,避免无限增长。

5. 隐私追踪

  • Cache API 与 IndexedDB 一样,可能被用于跨站追踪。主流浏览器(如 Safari 的 ITP)会对持久存储施加限制,例如自动清除 7 天未访问站点的缓存数据。

六、 与其他存储 API 的对比

特性Cache APIIndexedDBWeb StorageHTTP Cache
存储内容Request/Response 对象任意结构化数据键值对(字符串)原始响应(由浏览器管理)
API 类型异步(Promise)异步(Promise/回调)同步自动
粒度控制精确到每个请求精确到每条记录精确到每个键基于 HTTP 头(Cache-Control)
离线可用是(配合 Service Worker)仅当资源已缓存且未过期
更新策略完全程序控制程序控制程序控制由服务器头控制
跨标签共享同源共享同源共享localStorage: 共享;sessionStorage: 不共享共享(基于域名)
容量较大(几百 MB)较大(几百 MB+)较小(5-10 MB)由浏览器决定
适用场景资源缓存、离线应用大量结构化数据、离线数据简单配置、临时状态自动性能优化

七、 性能考量

1. 异步非阻塞

所有 Cache API 操作都是异步的,不会阻塞 UI 线程,适合在 Service Worker 或后台任务中大量使用。

2. 匹配效率

  • cache.match() 会按 URL 精确匹配,也支持忽略查询字符串、忽略方法等选项。
  • 对于大量缓存,匹配操作性能良好,但应避免在每次 fetch 事件中都进行复杂的匹配逻辑。

3. 存储开销

  • 缓存重复的响应会浪费空间,应合理规划缓存键和清理策略。
  • 响应 body 会被完整存储,大文件(如视频)可能快速消耗配额。

4. 持久化存储请求

  • 使用 navigator.storage.persist() 可降低数据被自动清除的风险,但用户仍可手动清除。

5. Navigation Preload 加速

  • 当用户发起导航请求时,Service Worker 启动需要时间。启用 Navigation Preload 后,浏览器会在 SW 启动同时并行发送网络请求。
  • 在 fetch 事件中可通过 event.preloadResponse 获取该预加载响应,与缓存匹配结果进行对比,选择最快或最新的响应返回。

八、 最佳实践

1. 版本化缓存

  • 使用带版本号的缓存名称(如 myapp-static-v2),在 Service Worker 激活时删除旧版本。
  • 避免将不同版本的资源混在同一个缓存中,防止不一致。

2. 合理选择缓存策略

  • 静态资源:预缓存 + 缓存优先(stale-while-revalidate)。
  • API 数据:网络优先 + 后台更新,或使用 Cache then Network 模式。
  • 离线回退:对导航请求预缓存离线页面。

3. 处理跨域资源

  • 确保跨域资源响应包含 Access-Control-Allow-Origin 且请求时设置 mode: 'cors'
  • 对于不支持 CORS 的资源(如某些 CDN 字体),可以使用 no-cors 模式缓存,但响应内容不可读(opaque response),且不保证可靠更新。

4. 错误处理与回退

  • fetch 事件中始终处理网络异常,返回缓存的备用响应。
  • 记录缓存操作失败的情况,便于调试。

5. 清理未使用的缓存

  • 在 Service Worker 激活事件中,对比当前版本号,删除其他所有缓存仓库。
  • 定期清理过期数据(如超过 30 天未使用的 API 缓存)。

6. 避免缓存敏感数据

  • 不在缓存中存储用户凭证、支付信息等敏感内容。如需离线使用,应结合加密方案。

7. 使用 Workbox 抽象复杂性

  • Google 官方出品的 Workbox 库提供了生产级的缓存策略、预缓存清单生成、过期清理等功能。
  • 推荐在复杂 PWA 中使用 Workbox,避免重复造轮子,减少缓存管理漏洞。

九、 未来趋势

  1. 持久化存储的标准化:已落地,navigator.storage.persist() 广泛支持。
  2. 缓存分区(Partitioned Cache):已实施,Firefox 最早,Chrome 自 2023 年起默认启用。
  3. 更强的离线能力:随着 Web 应用日益复杂,Cache API 与 Background Sync、Periodic Sync 等 API 结合,实现更智能的后台更新。
  4. 新增趋势:Web Bundles API 与 Subresource Loading with Web Bundles 可能改变资源分发的缓存模式,Cache API 需配合处理 bundle 内资源。

十、 总结

Cache API 是现代 Web 开发中实现高性能、高可用性(特别是离线体验)的关键基础设施。它与 Service Worker 紧密结合,赋予开发者对网络请求响应的精细控制能力。

  • 优势:编程式缓存、异步非阻塞、支持跨域资源、容量大、与 Service Worker 深度集成。
  • 局限:仅在安全上下文可用、数据可能被自动清除、不适合存储敏感信息、API 相对底层(可借助 Workbox 简化)。
  • 适用场景:PWA 离线应用、静态资源缓存、API 数据缓存、渐进式性能优化。

合理使用 Cache API,结合良好的缓存策略和版本管理,可以大幅提升 Web 应用的可靠性、响应速度和用户体验。随着浏览器存储机制的演进,Cache API 将持续扮演“应用层缓存”的核心角色。