service worker 的 fetch 监听不预加载资源,预加载需通过 install 阶段 caches.addall() 或运行时 fetch()+cache.put() 实现;企业级策略重在分层控制缓存、显式声明 scope、分离静态壳页与动态内容、按 destination 三级分流请求,并在 activate 阶段强制清理旧缓存。

Service Worker 的 fetch 监听机制本身不预加载资源,它只响应已发出的请求;真正的“预加载”必须靠 install 阶段的 caches.addAll() 或运行时主动调用 fetch() + cache.put() 实现。企业级策略的关键不是“监听即预载”,而是分层控制:哪些资源必须离线可用、哪些可动态兜底、哪些绝不能缓存。
注册与作用域必须显式声明 scope
企业应用常部署在子路径(如 /app/、/admin/),若不指定 scope,默认只控制注册脚本所在目录及子目录,容易漏掉关键路由资源。
-
navigator.serviceWorker.register('/sw.js', { scope: '/app/' })才能让 SW 控制/app/api/和/app/assets/ - scope 值必须是相对于站点根目录的路径,且不能高于注册脚本路径(比如
sw.js在/js/sw.js时,scope: '/'会失败) - 注册后立即检查 DevTools > Application > Service Workers 页面,确认 “Scope” 列显示的是你预期的路径,否则 fetch 事件根本不会触发
install 阶段预缓存必须区分“核心壳”与“动态内容”
企业应用的 HTML 页面(如 /app/index.html)不能简单丢进 caches.addAll() —— 它通常含服务端注入的 token、用户信息或时间戳,缓存后会导致多用户状态错乱或过期渲染。
- 只预缓存确定不变的资源:
/app/shell.html(无动态内容的骨架页)、/app/runtime.js、/app/vendor.css、图标和字体等 - 避免缓存带查询参数的 URL:
/app/data.json?t=1713402325这类地址每次都不一样,caches.addAll()会失败或漏缓存 - 使用
new Request(url, { integrity })显式传入 SRI(Subresource Integrity)哈希值,防止缓存被篡改的资源
fetch 事件中按 destination 和 URL 模式做三级分流
企业场景下,不同资源类型对一致性、时效性、隐私性的要求差异极大,不能统一用“缓存优先”或“网络优先”。必须结合 request.destination 和正则匹配做精细判断。
- HTML 文档(
destination === 'document'):用「缓存优先 + 网络回退」,但需加版本号校验逻辑,避免旧壳页加载新 JS 后白屏 - API 请求(
request.url.includes('/api/')):禁用缓存,或仅对 GET 类型启用stale-while-revalidate,且必须加{ credentials: 'include' }否则 session 丢失 - 用户上传文件(
request.url.match(/\/uploads\//)):直接跳过缓存,返回fetch(request),否则可能返回他人私有文件
activate 阶段清理旧缓存是强制动作,不是可选项
企业系统迭代频繁,缓存名(如 'v1')一旦写死,后续所有更新都无效;但开发者常忽略 activate 中的清理逻辑,导致旧缓存残留、新 SW 读不到新资源。
- 每次变更缓存策略或资源列表,必须升级 cacheName(如从
'core-v1'改为'core-v2') - 在
activate事件里调用caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(caches.delete))) - 搭配
self.skipWaiting()和clients.claim(),确保新 SW 立即接管已打开页面,否则用户刷新后仍走旧逻辑
最容易被忽略的是 HTML 页面的缓存粒度——它既不能全量缓存(含动态内容),也不能完全不缓存(失去离线能力)。实际做法是拆出纯静态的 shell 页面单独缓存,并用 Cache-Control: no-cache 响应头让浏览器每次校验 HTML 是否更新,再由 SW 决定是否替换缓存。这个细节没处理好,整个离线策略就会在真实用户场景中失效。










