Service Worker 生命周期:注册、安装、激活、更新、作用域控制、调试技巧
Service Worker 是离线优先应用的核心引擎,它的生命周期与普通网页脚本完全不同。理解并掌控这一生命周期,是构建可靠离线体验的前提。本章从注册到更新,逐一拆解每个阶段的原理、实现方法与调试要点。
生命周期总览
Service Worker 的生命周期包含五个主要阶段:注册→安装→激活→运行→更新。每个阶段都是异步事件驱动的,开发者通过事件监听(
install、activate、fetch、message等)介入控制。阶段一:注册(Registration)
注册是 Service Worker 的入口,通过 navigator.serviceWorker.register() 方法启动。浏览器会下载脚本并解析,然后进入安装流程。
// 主线程中注册 service-worker.js,scope 限定在 /app/ 路径下
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js', {
scope: '/app/'
}).then((registration) => {
console.log('Service Worker 注册成功,作用域:', registration.scope);
}).catch((error) => {
console.error('注册失败:', error);
});
}
| 参数 | 说明 | 默认值 |
|---|---|---|
scriptURL | Service Worker 脚本的 URL(必须 HTTPS 或 localhost) | — |
scope | 控制路径范围(以 / 结尾) | 脚本所在目录的父目录 |
作用域规则:scope 必须大于或等于脚本路径的父目录。例如脚本在
/js/sw.js,scope 可以设为 / 但不能设为 /admin/(除非脚本放在 /admin/ 下)。阶段二:安装(Install)
一旦注册成功,Service Worker 会触发 install 事件,这是预缓存资源的黄金时机。使用 event.waitUntil() 延长安装过程,确保缓存完成后再激活。
// service-worker.js
self.addEventListener('install', (event) => {
console.log('Service Worker 正在安装...');
// 跳过等待,立即激活(常用在更新场景)
self.skipWaiting();
event.waitUntil(
caches.open('my-cache-v1').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]);
})
);
});
⚠ 重要:安装阶段应只添加关键静态资源,避免缓存用户私有数据。若
addAll 中任一请求失败,整个安装过程将被拒绝,Service Worker 不会激活。阶段三:激活(Activate)
安装成功后进入激活阶段,触发 activate 事件。在这里清理旧版本缓存,确保新的 Service Worker 完全控制客户端。
self.addEventListener('activate', (event) => {
const cacheWhitelist = ['my-cache-v2']; // 当前使用的缓存版本
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (!cacheWhitelist.includes(cacheName)) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => {
// 立即控制所有客户端(不一定要等到页面再次加载)
return self.clients.claim();
})
);
});
阶段四:更新(Update)
Service Worker 的更新机制是其独特之处。当浏览器检测到脚本有字节差异时,会触发新的安装流程,同时旧版本继续运行直到所有页面关闭。
触发更新的场景:
- 用户在打开的页面中浏览器每隔 24 小时自动检查一次。
- 调用
registration.update()强制检查。 - 页面跳转到新的 Service Worker 作用域范围内。
// 在注册对象上手动触发更新检查
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then((reg) => {
// 每 60 秒检查一次
setInterval(() => {
reg.update();
}, 60 * 1000);
});
}
更新流程要点:新版本 Service Worker 会进入“等待”状态(install 完成后不立即激活),直到所有旧版本的页面都被关闭。使用
self.skipWaiting() 可跳过等待,搭配 self.clients.claim() 立即接管页面。作用域控制(Scope Control)
作用域决定了 Service Worker 拦截哪些页面的网络请求。浏览器通过 scope 参数与 Content-Type 头限制确保安全性。常见用例对比:
| 脚本位置 | 设置的 scope | 实际作用域 | 说明 |
|---|---|---|---|
/sw.js | / | / | 控制所有页面 |
/app/sw.js | /app/ | /app/ | 仅控制 /app/ 路径 |
/js/sw.js | / | 拒绝(scope 高于脚本父目录) | 需要设置 Service-Worker-Allowed 响应头 |
如果需要扩大作用域,服务端必须在 Service Worker 脚本的 HTTP 响应中添加:
Service-Worker-Allowed: /
调试技巧
在浏览器 DevTools 中调试 Service Worker 生命周期是最直接的方式。以下是关键操作与命令:
- 查看注册列表:在 Chrome DevTools → Application → Service Workers 面板。
- 强制跳过等待:勾选“Update on reload”让新版本立即接管。
- 模拟离线:在 Network 面板勾选“Offline”或直接断开连接。
- 查看缓存内容:Application → Cache → Cache Storage。
- 发送消息调试:主线程向 Service Worker 发送消息获取内部状态。
// 主线程发送消息
navigator.serviceWorker.controller.postMessage({
type: 'GET_STATE'
});
// Service Worker 接收消息并回复
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_STATE') {
event.ports[0].postMessage({
cacheVersion: 'v2',
clientsCount: clients.length
});
}
});
日志标记技巧:在 Service Worker 中使用
console.log() 配合 [SW] 前缀,再在 DevTools Console 中过滤,可快速区分主线程与 Worker 输出。常见错误处理
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
注册返回 SecurityError | 页面必须通过 HTTPS 或 localhost 加载 | 部署到 HTTPS 或使用本地开发服务器 |
install 事件中 addAll 失败 | 某个资源 404 或网络不可达 | 检查所有预缓存资源有效性 |
| 新版本不生效 | skipWaiting 未调用且旧页面未关闭 | 在 install 事件中调用 self.skipWaiting() |
| 作用域超出限制 | scope 大于脚本父目录且缺少权限 | 设置 Service-Worker-Allowed 响应头 |
| 缓存未更新 | activate 事件未清理旧缓存 | 在 activate 中根据白名单删除旧缓存 |
性能与稳定性优化建议
- 预缓存体积控制:尽量只缓存首次访问必须的资源(HTML、CSS、关键 JS),将非关键资源留到运行时缓存。
- 使用版本号管理缓存:在缓存名称中嵌入版本号(如
my-cache-v2),方便在 activate 中迭代。 - 避免频繁 update:每隔 5 分钟调用
reg.update()而不是每秒,减少带宽和计算开销。 - 谨慎使用
skipWaiting+clients.claim:推荐在用户点击“立即更新”按钮或页面跳转后调用,而非无条件强占,避免中途打断用户操作。 - 监控生命周期事件:在生产环境通过
navigator.serviceWorker.getRegistration()上报安装/激活状态到日志服务。