直接caches.match(event.request)会出问题,因其不区分请求类型,导致POST、带credentials的登录请求及API响应被错误匹配缓存;应按event.request.destination分流处理:document需兜底,script/style可缓存,json/empty等必须直连网络。

为什么直接 caches.match(event.request) 会出问题
这行代码看似能兜底所有请求,但实际会让 POST 表单提交静默失败、带 credentials: 'include' 的登录请求返回空响应,甚至把 API 接口也从缓存里硬塞给用户。根本原因是它不区分请求类型——fetch('/api/login', { method: 'POST' }) 和 fetch('/app.js') 都被当成普通资源去匹配缓存,而缓存里根本没有 POST 响应。
真正该做的,是按 event.request.destination 分流:
-
event.request.destination === 'document'→ 必须处理,否则离线打不开页面 -
event.request.destination === 'script'或'style'或'image'→ 可安全缓存,优先返回 -
event.request.destination === 'json'或'empty'→ 不该走缓存,直接fetch(event.request)
install 阶段该用 cache.addAll() 还是 cache.put()
cache.addAll() 简洁,但极其脆弱:列表里只要一个资源 404 或跨域失败,整个缓存就中断,什么都不会写入。适合打包后路径绝对稳定的项目(比如构建输出的 /static/js/main.8a2b.js)。
cache.put() 更可控,尤其适合处理 HTML 页面本身:
立即学习“前端免费学习笔记(深入)”;
- 对
index.html,可用new Response('<h1>Offline</h1>', { headers: { 'Content-Type': 'text/html' } })构造 fallback 响应,避免白屏 - 对图片或字体这类非关键资源,可捕获 fetch 错误后跳过,不中断 install 流程
- 注意:
cache.put()的Request参数必须和后续fetch()的 URL 完全一致(含大小写、查询参数)
离线时 HTML 能打开,但页面空白或功能缺失怎么办
这是最典型的缓存不完整现象,控制台报 Failed to load resource: net::ERR_FAILED,本质是只缓存了 /index.html,却漏了它依赖的 /app.js 或 /style.css。
正确做法是把 HTML 及其所有直接依赖一并列进预缓存列表:
- 路径必须是相对根路径,比如
['/', '/style.css', '/app.js', '/logo.png'],其中/就是index.html - 不要缓存带查询参数的 URL(如
/data.json?v=1.2),不同参数会被视为不同键,缓存空间很快爆掉 - 如果 HTML 里有动态加载的模块(比如
import('./feature.js')),这些路径也得手动加进缓存列表,否则离线时 import 失败
fetch 事件里怎么让 HTML 走缓存又不让 API 走缓存
核心是两层判断:先看 destination,再看 URL 模式。例如:
self.addEventListener('fetch', event => {
const { request } = event;
if (request.destination === 'document') {
// 页面 HTML:缓存优先,无则 fallback
event.respondWith(
caches.match(request).then(r => r || caches.match('/offline.html'))
);
} else if (request.destination === 'script' || request.destination === 'style') {
// JS/CSS:缓存优先,无则网络
event.respondWith(
caches.match(request).then(r => r || fetch(request))
);
} else if (request.url.startsWith(self.location.origin + '/api/')) {
// API:网络优先,失败才回退缓存(谨慎!)
event.respondWith(
fetch(request).catch(() => caches.match(request))
);
}
});
这里容易被忽略的是:API 回退缓存必须有明确业务语义(比如商品列表可展示旧数据),不能无差别套用。否则用户刷新页面看到的永远是昨天的订单状态。



















