Service Worker 与 Cache API

Service Worker 与 Cache API 在请求拦截、离线能力和可编程缓存中的角色分工。

#tech / dev / frontend #type / concept #status / evergreen

[!info] related notes

Service Worker 与 Cache API

Service Worker 和 Cache API 经常一起出现,但它们不是同一个层级的概念。

一句话定义

Service Worker 是浏览器里的可编程请求拦截层,Cache API 是它常用的一种缓存存储能力;两者配合后,前端就能更主动地控制资源缓存和离线体验。

它们分别做什么

Service Worker

  • 拦截网络请求
  • 决定请求走网络、走缓存还是混合策略
  • 常见于离线页面、资源缓存、后台同步、推送能力

Cache API

  • 存储请求和响应的映射
  • 更像面向 Request/Response 的可编程缓存仓库
  • 常被 Service Worker 使用,但不等于只有 Service Worker 能用

Service Worker 生命周期

Register → Install → (Waiting) → Activate → Fetch

1. Register(注册)

navigator.serviceWorker.register('/sw.js', { scope: '/' });
  • scope 决定 SW 能拦截哪些 URL 下的请求
  • SW 脚本必须与 scope 同源

2. Install(安装)

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll(['/index.html', '/style.css', '/app.js']);
    })
  );
});
  • 通常用于预缓存关键资源
  • event.waitUntil() 延迟激活直到预缓存完成

3. Waiting(等待)

如果旧版 SW 仍在控制页面,新版会等待。self.skipWaiting() 可强制立即激活——但要小心,新版 SW 可能与旧页面状态不兼容。

4. Activate(激活)

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((names) => {
      return Promise.all(
        names.filter((name) => name !== 'v1').map((name) => caches.delete(name))
      );
    })
  );
  self.clients.claim();
});
  • 通常用于清理旧缓存
  • clients.claim() 让新 SW 立即接管已打开的页面

5. Fetch(拦截请求)

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});
  • 每个受控页面的网络请求都会触发
  • SW 可以拦截、修改或从缓存返回

Cache API 方法

// 打开或创建命名缓存
const cache = await caches.open('my-cache');

// 存储请求/响应对
await cache.put(request, response);

// 查找匹配的响应
const response = await cache.match(request);

// 删除匹配条目
await cache.delete(request);

// 列出所有请求键
const keys = await cache.keys();

// 删除整个命名缓存
await caches.delete('my-cache');

[!warning] Cache API 存储的是 Request/Response 对象,不是任意 key-value。不能用它存普通数据。

常见缓存策略

Cache First(缓存优先)

// 先查缓存,没有就走网络
const cached = await caches.match(request);
return cached || fetch(request);

适用:带哈希的静态资源。

Network First(网络优先)

try {
  return await fetch(request);
} catch {
  return await caches.match(request);
}

适用:HTML 页面、需要新鲜数据的 API。

Stale While Revalidate(过期再验证)

const cached = await caches.match(request);
const networkPromise = fetch(request).then((response) => {
  caches.open('v1').then((cache) => cache.put(request, response.clone()));
  return response;
});
return cached || networkPromise;

适用:可以接受短暂旧数据的资源。

Cache Only

return caches.match(request);

适用:纯离线资源,从不更新。

Network Only

return fetch(request);

适用:非 GET 请求、分析埋点。

注册作用域

  • SW 只能控制 scope 路径下的页面
  • SW 脚本必须与 scope 同源
  • Service-Worker-Allowed 响应头可以扩展 scope(不超过 origin)

与 HTTP 缓存的交互

常见陷阱:SW 的 fetch() 默认会经过浏览器的 HTTP 缓存。如果 HTTP 缓存中有有效缓存,SW 拿到的可能不是最新内容。

绕过方式:

fetch(url, { cache: 'no-store' })  // 完全绕过 HTTP 缓存
fetch(url, { cache: 'reload' })    // 忽略缓存,强制验证

为什么它和 HTTP 缓存不一样

  • HTTP 缓存更依赖浏览器和服务器约定
  • Service Worker + Cache API 更偏前端主动控制
  • 前者更像协议层缓存,后者更像应用层缓存策略

常见误区

  • 把 Service Worker 当成”更强版 localStorage”
  • 把 Cache API 当成普通 key-value 存储
  • 以为有了 Service Worker 就不需要理解 HTTP 缓存

最短记忆方式

Service Worker 决定怎么拦,Cache API 决定缓存放哪。

创建于 2026/3/19 更新于 2026/5/27