serviceWorker 是什么
Service workers essentially act as proxy servers that sit between web applications, the browser, and the network
Service Worker 本质上充当位于 Web 应用程序、浏览器和网络(如果可用)之间的代理服务器。
使用场景介绍
1. 静态资源缓存与优化(Cache-First / Stale-While-Revalidate)
index.html
html
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/cache-static-sw.js')
.then((registration) => {
console.log('[SW] Service Worker registered with scope:', registration.scope);
})
.catch((error) => {
console.error('[SW] Service Worker registration failed:', error);
});
}
</script>
sw.js
javascript
/* eslint-disable no-restricted-globals */
const CACHE_NAME_PUBLIC = 'cdn-static-cache-v1';
const CACHE_NAME_VENDORS = 'cdn-static-cache-vendors-v1';
// 匹配规则
const PUBLIC_REGEX = /^https:\/\/cdn-static\.scm321\.com\/public\/.*\.(js|css)/;
const VENDORS_REGEX = /^https:\/\/cdn-static\.scm321\.com\/scm-saas-web\/.*\.(js|css)/;
self.addEventListener('install', (event) => {
console.debug('[SW] Installed');
// 跳过等待,直接激活
// 正常流程如下
// 1. 浏览器检测到 Service Worker 文件有变化。
// 2. 下载新版本 → 触发 `install` 事件 → 进入 `waiting` 状态。
// 3. 只有当所有旧版控制的页面都关闭后,新版才会被激活。
// 4. 新版激活后 → 触发 `activate` 事件 → 开始接管新的页面。
// ### `self.skipWaiting()` 的作用:
// 1. 强制让处于 waiting 状态的 Service Worker 立即激活。
// 2. 不再等待旧版控制的页面全部关闭。
// 3. 新版会马上进入 `activate` 阶段,但是旧页面(旧 clients)仍由旧版 SW 控制,直到这些页面刷新或重新加载。
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.debug('[SW] Activated');
// 等待客户端接管
// self.clients.claim() 作用
// 正常流程如下
// 新版 Service Worker 激活后(`activate` 事件触发),
// 默认情况下,它只会接管**:
// 1. 之后新打开的页面,
// 2.已被强制刷新的页面。
// 3. 旧页面(clients)仍然被旧版 SW 控制,直到用户刷新它们。
// ### `self.clients.claim()` 解决的问题:
// 让激活的 SW 直接接管所有正在打开的页面(clients) ,无需用户手动刷新。
// 适合实现「即时生效」的场景,比如:
// 1. 页面首次打开就受新版 SW 控制。
// 2。 静态缓存、缓存策略变更后立即接管。
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', (event) => {
const { url } = event.request;
if (PUBLIC_REGEX.test(url)) {
event.respondWith(cacheFirst(event.request, CACHE_NAME_PUBLIC));
return;
}
if (VENDORS_REGEX.test(url)) {
event.respondWith(cacheFirst(event.request, CACHE_NAME_VENDORS));
}
});
// CacheFirst 策略函数
async function cacheFirst(request, cacheName) {
const cache = await caches.open(cacheName);
const cachedResponse = await cache.match(request, { ignoreSearch: true });
if (cachedResponse) {
console.debug('[SW] Cache Hit:', request.url);
return cachedResponse;
}
const networkResponse = await fetch(request);
if (networkResponse.ok) {
console.debug('[SW] Network Response OK:', request.url);
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
展示结果 缓存没有时显示如下

再次刷新

问题记录
Q. network中size列显示了ServiceWorker 但是时间时间没有减少
A. 这个问题说明serviceWorker代理已经生效,但是没有命中缓存,可在 timing 中查看正式的response信息,可能还是走到了兜底的networkResponse中

Q. 为什么本地测试已经生效,发版后没有生效
- service-worker 要求必须是https;localhost浏览器为了调试方便也会认为是安全的;因此确认域名是否是https的
- 如果访问的域名和service-worker注册的域名不一致,也就是跨域了,则资源的reponse必须设置允许跨域
- 跨域时,发起请求的request mod 必须为cors(可以打印看下request的信息),也就是说是用script标签发起的请求 必须在 script 标签里添加crossorigin="anonymous"
2. 消息推送
- 依赖浏览器厂商,国内基本不可用
- 本地测试需要开启代理,而且不稳定,可以多试几次,只适合学习使用
- github代码地址,成功效果如下图
总结
- 推送时系统相同tag只显示一次
- 离线推送一定要使用service-worker,因为浏览器关闭时js是无法执行的,而sw的注册是浏览器级别的,可以完成离线推送的能力
- 依赖浏览器运营商
🧭 各大浏览器推送服务提供商(Push Service URL 对应关系)
浏览器 | 推送服务运营商名称 | Push Service URL 前缀 |
---|---|---|
Chrome | Google Firebase Cloud Messaging (FCM) | https://fcm.googleapis.com/fcm/send/ |
Firefox | Mozilla Push Service | https://updates.push.services.mozilla.com/push/ |
Edge | Microsoft Push Notification Service (MPNS) | https://wns2-par02p.notify.windows.com/w/ |
Safari (macOS, iOS) | Apple Push Notification Service (APNs) | 原生推送系统,不使用标准 Web Push endpoint |
Opera | 基于 Chromium,使用 Google FCM | https://fcm.googleapis.com/fcm/send/ |
Brave | 使用 Google FCM | https://fcm.googleapis.com/fcm/send/ |
Samsung Internet | 使用 Google FCM | https://fcm.googleapis.com/fcm/send/ |
3. 导航请求预加载(Navigation Preload)
- 只针对html有效,适合ssr和pwa
4. 处理请求重复问题
参考文章 前端如何彻底解决重复请求问题
- 优点,网络请求都可以在network里面清楚的看到,方便查看堆栈信息
- 缺点:必须serviceWorker激活后才能生效
sw.js
javascript
// service worker
// 使用 CDN 引入 js-sha256
importScripts(
"https://cdn.jsdelivr.net/npm/js-sha256@0.9.0/build/sha256.min.js",
);
self.addEventListener("install", (event) => {
console.debug("[SW] Installed");
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
console.debug("[SW] Activated");
event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", (event) => {
if (event.request.url.includes("api")) {
event.respondWith(handleFetch(event));
return;
}
});
const depsRequestCache = new Map();
const realRequestCache = new Map();
const handleFetch = async (event) => {
const hash = sha256.create();
hash.update(
JSON.stringify({
url: event.request.url,
method: event.request.method,
body: event.request.body,
}),
);
const fetchKey = hash.hex().slice(0, 40);
if (!realRequestCache.get(fetchKey)) {
const promise = fetch(event.request).then((result) => {
depsRequestCache.get(fetchKey)?.forEach((item) => {
item.resolve(result.clone());
});
depsRequestCache.delete(fetchKey);
realRequestCache.delete(fetchKey);
return result;
});
realRequestCache.set(fetchKey, promise);
}
const deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
depsRequestCache.set(fetchKey, [
...(depsRequestCache.get(fetchKey) || []),
deferred,
]);
event.request.signal.onabort = () => {
depsRequestCache.get(fetchKey)?.forEach((item) => {
item.reject(new Error("abort"));
});
depsRequestCache.delete(fetchKey);
};
return deferred.promise;
};
api介绍
- register
js
register(scriptURL)
register(scriptURL, options)
- 在同一个作用域(scope)下,Service Worker 只能注册一个。
- 同一页面 只能有一个激活状态的 Service Worker(按作用域匹配最近的)。
js
navigator.serviceWorker.register('/sw1.js', { scope: '/' });
navigator.serviceWorker.register('/sw2.js', { scope: '/' }); // 这会替换掉 sw1.js
navigator.serviceWorker.register('/sw-main.js', { scope: '/' });
navigator.serviceWorker.register('/sw-admin.js', { scope: '/admin/' });
navigator.serviceWorker.register('/sw-shop.js', { scope: '/shop/' });
- 访问
/
时,只有sw-main.js
生效。 - 访问
/admin/
时,sw-admin.js
生效。 - 访问
/shop/
时,sw-shop.js
生效。
- cacheStorage
- cache
- clients
- client