fetch事件无法实现真正“永不过期”,需通过Service Worker显式控制缓存生命周期:仅对/static/等路径资源调用caches.match()且不fallback网络,配合install阶段cache.addAll()预置缓存,并通过版本化cacheName确保升级安全。

fetch 事件本身不能让资源“永不过期”,但你可以通过 Service Worker 主动控制缓存生命周期,绕过浏览器默认的 HTTP 缓存时效限制。关键不是禁用过期,而是拒绝让缓存失效逻辑交由服务器或浏览器决定——所有判断权收归你手。
为什么不能依赖 Cache-Control: immutable 或 max-age=31536000
Cache-Control: immutable 或 max-age=31536000
这些 HTTP 头在离线时可能被忽略,且一旦服务端响应头变更(比如 CDN 自动注入新 Cache-Control),前端就失去控制。更严重的是:fetch() 在 Service Worker 中发起的请求,其响应默认不继承页面请求的缓存策略;你若不显式调用 cache.put(),哪怕响应带了 max-age=1000y,也不会进 Cache API。
所以“永不过期”必须靠代码显式维持,而不是靠声明。
- HTTP 缓存头只影响浏览器原生缓存(memory/disk cache),不影响
caches.open()创建的 Cache API 实例 -
cache.match()查找时不校验响应头里的Expires或max-age,它只认“有没有这个 request → response 对” - 真正决定“是否过期”的,是你在
fetch事件中是否选择跳过网络、直接返回缓存
如何用 fetch 事件实现“只要缓存存在就永不走网络”
fetch 事件实现“只要缓存存在就永不走网络”核心是:对目标资源(如 /app.js、/logo.svg)始终走 cache.match(),且不补发网络请求更新它。这相当于把缓存当作只读静态仓库。
示例逻辑(适用于确定永不变更的资源):
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
<p>// 只对明确标记为“永不过期”的资源启用该策略
if (url.pathname.startsWith('/static/') ||
['.js', '.css', '.woff2', '.png'].some(ext => url.pathname.endsWith(ext))) {</p><pre class="brush:php;toolbar:false;">event.respondWith(
caches.match(event.request).then(response => {
if (response) return response;
// 永不过期策略下,不 fallback 到 fetch() —— 没缓存就失败
// (你应在 install 阶段确保它们一定存在)
return new Response('Not cached', { status: 404 });
})
);} });
- 必须配合
install事件中的cache.addAll(),确保这些资源首次就被写入缓存,否则后续永远 404 - 不要在该分支里调用
fetch(event.request),否则就不是“永不过期”,而是“缓存优先”了 - 路径匹配要精确,避免误伤 API 请求(比如
/api/user.js这种命名冲突)
“永不过期”带来的真实风险和应对方式
这不是一个安全的默认策略,而是一种强契约:你承诺这些文件内容绝对不变,连哈希都不改。一旦你上线新版 JS 却没更新缓存名或没触发 Service Worker 更新,用户将卡在旧版,且无法自动恢复。
- 版本升级必须修改
cacheName(如从v1改为v2),并在activate中清理旧缓存,否则新旧缓存共存导致不可预测行为 - 不能依赖
importScripts()动态加载外部脚本并期望它被“永不过期”——importScripts不走fetch事件,不受你拦截逻辑控制 - CDN 回源失败时,如果资源不在缓存里,用户将看到空白页或崩溃,而非优雅降级——你得提前预判哪些资源属于“不可缺失”
真正的“永不过期”只适合极少数场景:字体文件、品牌 logo、已冻结的 polyfill 脚本等。绝大多数 JS/CSS 应使用带版本号的文件名(app.a1b2c3.js)+ Cache First 策略,而非字面意义的“永不过期”。










