实现秒开的关键是页面首次请求即命中缓存;CacheStorage 缓存 Response 而非 DOM,需预缓存 HTML/CSS/JS 等静态资源,并在 fetch 事件中对 document 请求优先匹配缓存,注意 URL 一致性、clone 响应流及 ignoreSearch 配置。

实现秒开,关键不是“缓存多少”,而是“缓存是否在页面首次请求时就命中”。CacheStorage 本身不缓存 DOM,它缓存的是 网络请求对应的 Response 对象;而 DOM 是浏览器解析 HTML 后生成的。所谓“缓存原始 DOM 所需的静态资源”,本质是提前缓存 HTML、CSS、JS、字体、关键图片等,让页面一发起请求就能从本地返回完整响应,跳过网络等待。
预缓存核心资源(install 阶段)
在 Service Worker 安装时,把首屏必需的资源一次性加入缓存,确保离线或弱网下也能立即响应 HTML 请求。
- HTML 页面(如
/、/index.html)必须包含在预缓存列表中,否则用户首次访问仍要走网络 - CSS 和 JS 要与 HTML 中实际引用的 URL 完全一致:包括路径大小写、尾部斜杠、查询参数(如
app.js?v=2.1.0) - 使用
caches.open('v1').then(cache => cache.addAll([...])),并在install事件中调用event.waitUntil()确保缓存完成才进入激活状态
拦截 HTML 请求并优先返回缓存(fetch 阶段)
对页面级请求(destination === 'document'),必须在 fetch 事件开头就调用 event.respondWith(),否则浏览器会直接发网络请求,缓存彻底失效。
- 检查
event.request.destination,只对'document'类型请求走缓存优先逻辑,避免把图片、脚本误判为页面 - 用
caches.match(event.request)查找匹配缓存;若命中,直接返回;未命中,再fetch(event.request)并将响应存入缓存(注意 clone) - 不要把
return fetch(...)写在 if 外层——这会导致未缓存的 HTML 请求永远绕过缓存
处理带查询参数的静态资源
页面引用 /style.css?v=20260510,但缓存里只有 /style.css,caches.match() 默认不匹配。解决方法分场景:
- 预缓存阶段:确保
cache.addAll()列表中的路径和页面实际请求地址完全一致(推荐加哈希后缀,如style.a1b2c3.css) - 运行时缓存:对 CSS/JS 等资源,在
caches.match()传第二个参数{ ignoreSearch: true },或用new Request(url, { ignoreSearch: true })构造匹配请求 - 调试时打开 DevTools → Application → Cache Storage,手动展开查看缓存 key 的真实格式,确认是否含查询参数
缓存响应前必须 clone,避免流被消耗
fetch 得到的 Response 是一个只读、单次读取的流。如果直接 return 给浏览器,后续再想 response.clone().json() 或存进 cache,会报 “body stream already read” 错误。
- 正确做法:拿到 response 后,先
const cachedResponse = response.clone(),一个用于返回浏览器,一个用于cache.put(request, cachedResponse) - 尤其在 HTML 缓存逻辑中,若需解析 HTML 内容做进一步处理(如注入数据),clone 更不可少
- 所有涉及缓存写入的操作,都应基于 clone 后的副本,原 response 直接返回

















