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 决定缓存放哪。