Service Worker 通过在 fetch 中嵌入 If-None-Match/If-Modified-Since 条件请求,协同服务端 ETag/Last-Modified 实现秒级热更新:304 时返回缓存,200 时更新缓存并返回新资源。

Service Worker 本身不直接参与 HTTP 协商缓存(如 ETag、Last-Modified),但它可以与协商缓存机制深度协同,实现“秒级热更新”——即资源内容变更后,用户刷新页面即刻加载最新版本,既不破坏强缓存效率,又避免手动清缓存或硬刷新。
为什么需要协同?单靠一方做不到秒级热更新
HTTP 协商缓存依赖服务器响应头(如 ETag)在每次请求时由浏览器自动比对,但前提是请求必须发到服务端;而 Service Worker 默认可能直接从缓存返回资源(cache-first),跳过网络验证,导致用户看到旧内容。反之,若 Service Worker 强制走网络(network-first),又失去缓存提速价值。真正的“秒级热更新”,是在保留本地缓存前提下,让 Service Worker 主动触发一次轻量级协商验证,并在确认变更后自动更新缓存。
关键实现:fetch 中嵌入协商逻辑 + 版本感知
核心思路是:Service Worker 在拦截请求时,不简单地 caches.match() 或 fetch(),而是先尝试带条件请求(If-None-Match / If-Modified-Since),利用服务器返回的 304 Not Modified 或 200 OK 做决策:
- 收到
304→ 缓存未变,直接返回已缓存响应(毫秒级) - 收到
200→ 资源已更新,将新响应写入缓存并返回(用户无感完成热更新)
示例代码(sw.js 中 fetch 事件处理):
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 仅对静态资源启用协商更新策略
if (!/\.js$|\.css$|\.html$/.test(url.pathname)) return;
<p>event.respondWith(
caches.match(event.request).then(cached => {
// 有缓存 → 发起带 ETag 的条件请求
if (cached) {
return fetch(event.request, {
headers: {
'If-None-Match': cached.headers.get('ETag'),
'If-Modified-Since': cached.headers.get('Last-Modified')
}
}).then(response => {
if (response.status === 304) {
// 未修改,返回原缓存
return cached;
} else {
// 已更新,存新响应并返回
return caches.open('v2-static').then(cache => {
cache.put(event.request, response.clone());
return response;
});
}
}).catch(() => cached); // 网络异常时回退缓存
}</p><pre class="brush:php;toolbar:false;"> // 无缓存 → 直接请求并缓存
return fetch(event.request).then(response => {
if (response.ok) {
return caches.open('v2-static').then(cache => {
cache.put(event.request, response.clone());
return response;
});
}
return response;
});
})
); });
配套服务端必须支持协商头
前端协同生效的前提,是后端对静态资源响应正确设置协商缓存头:
- 首次响应必须包含
ETag或Last-Modified(推荐ETag,更精准) - 响应头中需含
Cache-Control: public, must-revalidate,明确告知浏览器“可缓存,但每次用前必须验证” - Nginx 示例配置:
location ~* \.(js|css|html)$ {
add_header ETag $mtime_iso8601; # 或使用更稳定的哈希生成 ETag
add_header Cache-Control "public, must-revalidate, max-age=0";
expires off;
}规避常见陷阱:缓存名升级 + 激活清理
单纯改逻辑不够,还需配合 Service Worker 生命周期管理:
- 每次更新策略或资源结构,应升级缓存名(如从
v1-static→v2-static),避免旧缓存干扰 - 在
activate阶段主动删除旧缓存,防止磁盘堆积和误读 - 注册时加时间戳参数(
navigator.serviceWorker.register('/sw.js?t='+Date.now()))可绕过浏览器对 SW 文件本身的强缓存,确保新版脚本及时加载










