Service Worker
参考:web.dev Service Worker 生命周期
1. 什么是 Service Worker?
Web Worker 是临时的,每次做的事情的结果不能被持久存下来;下次同样的复杂操作还得重新做。能不能有一个 Worker 一直持久存在,随时接受主线程命令?基于这种需求,推出了最初的 Service Worker:在 Web Worker 的基础上加上持久离线缓存能力。
它本质上也是浏览器缓存资源用的,只不过它不仅仅是 Cache,而是通过 worker 的方式进一步优化。它基于 h5 的 Web Worker,所以不会阻塞当前 JS 线程,核心原理:
- 后台线程:独立于当前网页线程。
- 网络代理:在网页发起请求时代理,缓存文件。
Service Worker 充当服务器与浏览器之间的中间人角色。如果网站中注册了 Service Worker,它可以拦截当前网站所有的请求并进行判断:
- 需要向服务器发请求的 → 转给服务器。
- 可以直接使用缓存的 → 直接返回缓存,不再转给服务器。
Service Worker 必须基于 HTTPS 协议,因为涉及请求拦截,必须用 HTTPS 保障安全。
2. 生命周期与更新
生命周期包括:注册 register(或更新 update)、installing 安装、activated 激活、fetch 拦截请求。
event.waitUntil():在 install/activate 阶段延长生命周期,确保异步操作完成再切换状态。clients.claim():使 Service Worker 在激活后能立即控制未受控页面。- 第一次不会被 sw 控制,之后进入或刷新页面才会被控制。
触发更新的时机
- 导航到作用域内的页面。
- 更新
push和sync等功能事件(除非前 24 小时内已进行更新检查)。 - 仅在 Service Worker 网址发生变化时调用
.register()(应避免更改 worker 网址)。 - Service Worker 字节与浏览器已有的不同时。
更新过程(Soft Update)
- 由浏览器触发,逐字节比对不同时启动更新算法。
- 更新的 SW 与现有 SW 一起启动,并获取自己的
install事件。 - 安装成功后,更新的工作线程 wait,直到现有工作线程控制零个客户端;
self.skipWaiting()可跳过等待立即激活。 - 不要给 sw 设置不同的名字。
3. 与静态资源的差异
HTML 页面是网站的入口,它对其他资源文件的引用描述了资源间的依赖关系。Service Worker 与静态资源有所不同:Service Worker 不属于页面资源脚本,程序入口的执行点可能不是页面,而是 Service Worker 本身。如果给 Service Worker 增加 MD5,可能导致严重后果:
- 新的 HTML 未载入(页面中通常包含
register代码),Service Worker 就无法更新; - Service Worker 无法更新就无法载入新的 HTML。
虽然可以通过小心编码避免上述问题(如在 Service Worker 中对 HTML 采用网络优先缓存策略),但让软件能否正常更新依赖于小心编码,总是不如依赖固化的编译部署流程。
4. 注册与作用域
scope 参数可选,指定 Service Worker 控制内容的子目录。'./' 表示根网域下所有内容(默认值)。Service Worker 和网站同源,可为当前网站的所有请求做代理。
// 注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./serviceWorker.js', { scope: './' })
.then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
5. 离线缓存示例
// 离线缓存:install 阶段
this.addEventListener('install', function (event) {
event.waitUntil(
caches.open('v1').then(function (cache) {
return cache.addAll(['./index.html']);
})
);
});
// 拦截请求:fetch 阶段
this.addEventListener('fetch', function (event) {
event.respondWith(caches.match(event.request));
});
下面是更完整的 install/activate/fetch 示例,包含旧缓存清理:
const expectedCaches = ['static-v2'];
self.addEventListener('install', (event) => {
console.log('V2 installing…');
event.waitUntil(
caches.open('static-v2').then(cache => cache.add('/horse.svg'))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) return caches.delete(key);
})
)).then(() => {
console.log('V2 now ready to handle fetches!');
})
);
});
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.origin === location.origin && url.pathname === '/dog.svg') {
event.respondWith(caches.match('/horse.svg'));
}
});
fetch 用于拦截代理所有指定的请求,并进行对应操作,是最关键的阶段。
6. 注意事项
- Service Worker 运行在 Worker 上下文,不能访问 DOM。
- 设计为完全异步,同步 API(
XHR、localStorage)不能在 Service Worker 中使用。 - 出于安全考量,只能由 HTTPS 承载。
- Firefox 浏览器的隐私模式下 Service Worker 不可用。
- 生命周期与页面无关(没有关联页面时也可以启动,关联页面未关闭时也可以退出)。
- 在 Chrome DevTools 的 Application 面板可查看 Service Worker、Cache Storage;Network 中被缓存的文件
Size项显示为from Service Worker。
7. Service Worker API
8. Workbox 工具
importScripts 是 Service Worker 中加载 JS 的方式。
Workbox 的缓存分为两种:
- precache:在
installing阶段读取并缓存资源,适合跨域 CDN 和域内静态资源。 - runtime cache:在
install之后的activated和fetch阶段处理运行时缓存,对不同类型资源有不同策略:- Stale While Revalidate:命中缓存直接返回,同时后台更新缓存。
- Network First:优先尝试网络请求,成功后写入缓存。
- Cache First:优先读缓存,没有则发网络请求并写入缓存。
Workbox 源码亮点
- 通过 Proxy 按需依赖:子模块按需
importScript到线程中,通过 Proxy 代理全局对象Workbox。 - 通过
freeze冻结对外暴露 API:内部模块(如DBWrapper、Cache Wrapper、fetchWrapper、logger等)通过Object.freeze防止外部修改。
来源整理自:我的有道云笔记



