service worker无法实现真正的“全量版本镜像”代理,本质是通过语义化缓存名(如static-v2.3.1)、url精确匹配、install/activate生命周期控制模拟镜像;需避免缓存名复用、确保预缓存url与html请求完全一致、正则匹配静态资源、显式处理credentials请求、调用skipwaiting和clients.claim推进生命周期,并严格校验cors与mime类型。

Service Worker 无法实现真正的“全量版本镜像”代理——它不保存资源快照,也不隔离版本状态;所谓“镜像”,本质是靠缓存命名 + URL 精确匹配 + 生命周期控制模拟出来的语义化缓存策略。
缓存名必须带语义化版本号,且 install 阶段只写新缓存
Cache API 不支持原子替换:用 caches.open('static') 打开的是一个可变引用,cache.put() 会直接覆盖已有条目。若旧页面还在用这个缓存名,就可能读到被新 SW 覆盖的混杂资源。
正确做法:
- 缓存名显式包含版本标识,如
static-v2.3.1或static-20260414(避免用构建哈希,否则未变更时也触发重缓存) -
install事件中只打开并填充新缓存,绝不动旧缓存 - 预缓存列表(
cache.addAll()参数)里的每个 URL 必须与 HTML 中实际请求的 URL 完全一致:包括协议、域名、路径、查询参数、大小写、尾部/ - 例如 HTML 写了
<script src="/js/app.js?v=2.3.1"></script>,预缓存数组里就得写'/js/app.js?v=2.3.1',不能写'js/app.js'(相对路径会被解析为相对于sw.js位置)
fetch 事件中匹配静态资源,别信 request.destination
request.destination 在多个场景下不可靠:比如 fetch('/api/config.json', {mode: 'no-cors'}) 会让 destination 变成 'empty';字体在 Firefox 返回 'font',Chrome 是 'style';甚至某些 SVG 加载方式会让 destination 为空。
更稳妥的做法是正则匹配 URL 路径:
const isStaticResource = (request) => /\.(js|css|png|jpg|jpeg|gif|woff2?|svg|ico|webp)$/i.test(request.url);
注意点:
-
request.url是完整绝对 URL,开发时容易因localhostvs127.0.0.1不一致导致匹配失败 - HTML 页面建议单独判断:
request.destination === 'document'是安全的 - 带
credentials: 'include'的请求默认无法被缓存(credential mismatch),除非你显式传{ ignoreVary: true }给cache.match()
activate 阶段清理旧缓存,配合 skipWaiting 和 clients.claim
新 SW 注册后,默认处于 “waiting” 状态,直到所有旧页面关闭才会激活。这意味着即使你改了缓存逻辑,用户仍走旧逻辑——常见于刷新页面后发现缓存没更新。
必须手动推进生命周期:
- 在新 SW 文件开头加
self.skipWaiting(),让新 SW 跳过 waiting 直接进入 activating - 在
activate事件中调用self.clients.claim(),使新 SW 立即接管当前页面 -
activate里批量删除非当前版本的缓存:caches.delete('static-v2.2.0'),但不要删错——过滤条件得严格比对缓存名前缀或版本字符串 - 清理操作必须包裹在
event.waitUntil()中,否则可能中断
CDN 资源缓存失败?先查 CORS 和 MIME 类型
从 CDN 加载的资源(如 https://cdn.example.com/js/app.js)若 cache.put() 静默失败,大概率是服务端响应头缺失或错误:
- CORS 头必须允许跨域缓存:
Access-Control-Allow-Origin: *(或精确域名),否则cache.put()抛异常且不提示 - 服务器返回的
Content-Type必须是有效 MIME 类型,如text/javascript、image/png;若返回text/html(常见于 404 页面被当 JS 缓存),cache.put()会拒绝 - 检查 DevTools > Application > Cache Storage,确认对应缓存是否存在、条目是否为空
真正难处理的不是“怎么写”,而是“怎么让缓存名、URL、生命周期、CORS 四者严丝合缝地对齐——漏掉任意一环,镜像就变成幻影。










