Service Worker 怎样只缓存同源静态资源
来源:17golang原创
时间:2026-10-23 11:02:56 189浏览 收藏
Service Worker 只缓存同源静态资源,关键不是把每一次 fetch 都交给缓存,而是在 fetch 事件里先判断请求的 origin,再用 Request.destination 限定资源类型。命中缓存就直接返回;未命中时请求网络,并且只把状态正常、确实属于静态资源的响应副本放进缓存。
官方地址:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
- 同源判断使用
new URL(event.request.url).origin === self.location.origin,不要只比较字符串前缀。 - 用
Request.destination允许style、script、image、font等静态资源,排除document、fetch和empty。 - 缓存 API 不会自动按 HTTP 缓存头更新条目,缓存名要版本化,并在
activate中清理旧版本。
先确认 Service Worker 为什么没有接管请求
遇到“缓存没有生效”时,第一步不是修改 cache.put(),而是确认 Service Worker 能否运行。它属于 Worker,不能访问 DOM;注册文件必须来自安全上下文,通常是 HTTPS,开发时的 http://localhost 也被浏览器视为安全上下文。注册脚本的目录还决定默认作用域,脚本放在过深的目录时,页面可能根本不在它的控制范围内。
注册代码可以保持很小,把路径和作用域写清楚:
if ('serviceWorker' in navigator) {
// 注册路径应与实际脚本位置一致,scope 决定它能控制哪些页面。
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then((registration) => {
console.log('Service Worker registered:', registration.scope);
})
.catch((error) => {
// 注册失败先检查 HTTPS、脚本路径和作用域,而不是假设缓存逻辑有问题。
console.error('Service Worker registration failed:', error);
});
}
首次注册完成后,当前页面不一定马上由新 Worker 控制;页面刷新一次或使用 clients.claim() 都可能影响观察结果。这个现象常被误判成“同源条件没有命中”。

用 origin 和 destination 把缓存范围收窄
真正的筛选逻辑放在 fetch 事件里。new URL(request.url).origin 能把协议、主机和端口作为整体比较;相比用 startsWith('/assets/'),它不会因为路径前缀相似就把外部地址误纳入。
Request.destination 由浏览器描述请求将要用于什么目的,例如样式表、脚本、图片、字体或文档。示例只允许静态资源目的,明确跳过页面导航和接口请求:
const CACHE_NAME = 'static-assets-v1';
const STATIC_DESTINATIONS = new Set([
'style',
'script',
'image',
'font',
'audio',
'video'
]);
self.addEventListener('fetch', (event) => {
const request = event.request;
const requestURL = new URL(request.url);
const sameOrigin = requestURL.origin === self.location.origin;
// 只处理同源静态资源;document、fetch 和空 destination 留给网络层。
if (!sameOrigin || !STATIC_DESTINATIONS.has(request.destination)) {
return;
}
event.respondWith(cacheFirst(request));
});
这里的“同源”包含协议、主机和端口。即使两个地址看起来都来自同一个站点,只要端口不同,也不能当作同源。接口请求的 destination 通常是 empty,但不要只依赖这一点;origin 与资源用途应当同时判断。

缓存优先只保存可以复用的响应
对静态资源可以采用缓存优先:先查当前版本的 Cache,命中就返回;未命中时访问网络,收到成功响应后克隆一份写入缓存,原响应继续返回给页面。因为响应体只能被消费一次,写入缓存和返回页面必须使用不同的副本。
async function cacheFirst(request) {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(request);
if (cachedResponse) {
// 命中缓存直接返回,避免静态资源每次都访问网络。
return cachedResponse;
}
const networkResponse = await fetch(request);
// 只缓存成功响应,避免把 404 或服务器错误固定在缓存里。
if (networkResponse.ok) {
await cache.put(request, networkResponse.clone());
}
// 返回原响应给页面,clone 只用于保存另一份响应体。
return networkResponse;
}
Cache 的条目不会因为 HTTP 缓存头自动更新,也不会自动过期;应用需要自己决定何时替换或删除它们。若静态文件名不带内容哈希,部署新版本时更要更换缓存名,否则旧脚本或旧样式可能继续被命中。
用版本化缓存处理更新和旧条目
把清理动作放到 activate,保留当前版本,其余名称全部删除。event.waitUntil() 用来延长生命周期,确保异步清理完成前事件不会被过早结束:
self.addEventListener('activate', (event) => {
const keepCaches = new Set([CACHE_NAME]);
event.waitUntil(
caches.keys().then((cacheNames) => Promise.all(
cacheNames.map((cacheName) => {
// 只删除不在当前白名单中的旧缓存,避免误删其他用途的缓存。
if (!keepCaches.has(cacheName)) {
return caches.delete(cacheName);
}
return undefined;
})
))
);
});
排查时可以按这个顺序观察:注册是否成功、当前页面是否已被控制、请求是否同源、destination 是否在白名单、缓存名是否切换、网络响应是否为 ok。任意一层不满足,后面的 Cache.match 都不会给出预期结果。
相关问题
为什么接口也被 Service Worker 拦截了?
如果只监听了 fetch 却没有检查 destination,接口请求同样会进入处理器。为静态缓存建立明确白名单,并让 empty 等类型直接返回。
跨域 CDN 资源能不能放进这个缓存?
本文的目标是只缓存同源资源,所以跨源请求直接跳过。如果业务确实需要缓存 CDN,应单独确认 CORS、响应类型和缓存策略,不要把它混入同源白名单。
为什么改了代码还读到旧文件?
通常是旧缓存名仍在使用、旧 Worker 仍处于 waiting,或当前页面还没有被新 Worker 控制。提升缓存版本并在 activate 清理旧名,再重新加载受控页面。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
102 收藏
-
499 收藏
-
414 收藏
-
477 收藏
-
150 收藏
-
100 收藏
-
214 收藏
-
278 收藏
-
192 收藏
-
107 收藏
-
483 收藏
-
161 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习