本文对比了服务工作者线程(Service Worker)生命周期中的关键事件和属性,包括注册、安装、激活和控制各阶段。
通过详细表格展示了不同事件的触发时机、监听位置(主线程/服务线程)和典型用途,并重点分析了事件是否立即生效的特性差异。
文章还提供了实际代码示例,说明如何正确处理异步操作(如waitUntil和respondWith),确保服务工作者线程生命周期管理的可靠性。
最后总结了不同生效时机的适用场景,帮助开发者优化离线应用的用户体验和功能实现。
补充:
服务工作者线程更新流程的初始阶段是更新检查,下列事件可以触发更新检查:
触发事件 说明 导航 用户访问一个由 Service Worker 控制的作用域内的页面。 功能事件 发生 fetch、push、sync等受控功能事件时。脚本更新 调用 navigator.serviceWorker.register()时,如果本地已存在该脚本,但 URL 不同,则会立即获取新脚本进行检查。最大时长 即使没有触发事件,浏览器也会每隔 24 小时(或其他实现定义的间隔)自动检查一次更新。
关联阅读推荐
服务工作者线程生命周期事件/属性对比表
| 阶段 | 事件/属性 | 监听位置 | 是否立即生效 | 触发时机 | 典型用途 |
| 注册阶段 | navigator.serviceWorker.register() | 主线程 | 否(异步) | 注册服务工作者脚本 | 首次注册或更新SW |
| | registration 对象 | 主线程 | 立即 | 注册成功后返回 | 管理SW更新、订阅等 |
| 安装阶段 | install 事件 | 服务线程 | 是,但需等待waitUntil() | SW脚本首次下载或检测到更新时 | 预缓存资源、初始化DB |
| | registration.installing | 主线程 | 立即 | 安装过程中可用 | 检查安装状态 |
| 等待阶段 | registration.waiting | 主线程 | 立即 | 安装完成等待激活 | 提示用户更新、跳过等待 |
| 激活阶段 | activate 事件 | 服务线程 | 是,但需等待waitUntil() | SW激活成功,获得控制权 | 清理旧缓存、数据迁移 |
| | registration.active | 主线程 | 立即 | SW已激活 | 检查激活状态 |
| 控制阶段 | controllerchange 事件 | 主线程 | 立即 | 页面被新的SW控制时 | 更新UI、重新加载页面 |
| | navigator.serviceWorker.controller | 主线程 | 立即 | 当前控制页面的SW | 检查当前控制器 |
| 更新检查 | updatefound 事件 | 主线程 | 立即 | 检测到新版本SW开始安装 | 显示更新提示 |
| | registration.update() | 主线程 | 否(异步) | 手动触发更新检查 | 强制检查更新 |
| 就绪状态 | navigator.serviceWorker.ready | 主线程 | 否(Promise) | SW已激活并准备就绪 | 确保SW可用后执行操作 |
| 功能事件 | fetch 事件 | 服务线程 | 是,但需等待respondWith() | 发起网络请求时 | 拦截和处理请求 |
| | push 事件 | 服务线程 | 是,但需等待waitUntil() | 收到推送消息时 | 显示推送通知 |
| | sync 事件 | 服务线程 | 是,但需等待waitUntil() | 后台同步触发时 | 同步离线数据 |
| 错误处理 | statechange 事件 | 主线程 | 立即 | SW状态变化时 | 调试状态变化 |
error 事件 |
主线程 | 立即 | SW脚本加载失败时 | 错误处理和降级 |
|---|
| 阶段 | 事件/属性 | 监听位置 | 触发时机 |
|---|---|---|---|
| 更新检测 | updatefound 事件 |
主线程(在 registration 对象上) | 浏览器检测到新版本SW并开始安装 |
| 安装阶段 | install 事件 |
服务线程(在SW脚本内) | SW开始安装 |
| 激活阶段 | activate 事件 |
服务线程(在SW脚本内) | SW激活成功 |
| 控制变更 | controllerchange 事件 |
主线程(在 navigator.serviceWorker 上) | 页面被新SW控制 |
详细说明与示例代码
主线程监听的事件
javascript
// 注册服务工作者
const registration = await navigator.serviceWorker.register('/sw.js');
// 监听控制器变化
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('页面已被新的服务工作者控制');
});
// 监听更新发现
registration.addEventListener('updatefound', () => {
console.log('发现新版本服务工作者');
});
// 检查等待中的SW
if (registration.waiting) {
// 提示用户更新
}
服务线程内监听的事件
javascript
// sw.js 文件内
// 安装事件
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => cache.addAll(['/app.css']))
);
});
// 激活事件
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.map(key => caches.delete(key)))
)
);
});
// 拦截请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response =>
response || fetch(event.request)
)
);
});
生命周期流程图
主线程 服务线程
│ │
├─ register('sw.js') ──────────────────────▶│
│ │
│ 返回 registration 对象 │
│ │
│─监听 updatefound ────检测到更新───────────▶│
│ │
│ ├─ install 事件
│ │ (预缓存资源)
│ │
│ registration.waiting 变为真 │
│ │
│─调用 skipWaiting() ───跳过等待───────────▶│
│ │
│ ├─ activate 事件
│ │ (清理旧缓存)
│ │
│─监听 controllerchange ◀─获得控制权───────▶│
│ (页面被新SW控制) │
│ │
│─navigator.serviceWorker.ready───────────▶│
│ (SW准备就绪) │
│ │
└─监听 fetch/push/sync 等事件◄─处理事件─────▶│
主线程 服务线程
│ │
├─ register('sw.js') │
│ │
├─ 监听 registration.onupdatefound │
│ (检测到新版本) │
│ │
│ ┌──────────── 新版本下载 ──────▶│
│ │ │
│ │ ├─ 触发 install 事件
│ │ │ (安装开始)
│ │ │
│ │ ├─ 安装完成
│ │ │
│ │ ├─ 等待激活
│ │ │
│ │ (skipWaiting()) │
│ └──────────── 请求激活 ────────▶│
│ │
│ ├─ 触发 activate 事件
│ │ (激活开始)
│ │
├─ 触发 controllerchange
│ (页面被控制)
重要区别总结
-
位置不同:
-
主线程:管理SW注册、更新和状态监控
-
服务线程:处理具体功能逻辑(缓存、推送等)
-
-
时机不同:
-
主线程事件关注SW与页面的关系变化
-
服务线程事件关注SW自身状态和功能执行
-
-
控制权不同:
-
主线程可以主动触发更新、跳过等待
-
服务线程控制资源缓存和请求处理
-
服务工作者线程事件对比表
| 事件名称 | 触发时机 | 主要用途 | 是否立即生效 | 典型场景 |
|---|---|---|---|---|
| controllerchange | 当 navigator.serviceWorker.controller 发生变化时触发 |
检测页面是否被新的服务工作者控制 | 立即生效 | 页面刷新后检测是否由新SW控制,更新UI状态 |
| updatefound | 检测到新版本SW开始安装 | 提示用户有更新可用,可显示更新提示 | 尚未生效 | 检测到新版本SW,显示"有更新可用"提示 |
| activate | 当服务工作者激活成功,获得控制权时触发 | 清理旧缓存、迁移数据等准备工作 | 已生效但尚未控制页面 | 删除旧缓存,为控制客户端做准备 |
详细说明:
controllerchange
-
触发条件 :
navigator.serviceWorker.controller属性值改变 -
特点:表明页面已被新的服务工作者控制
-
示例使用:
javascript
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('当前页面已被新的服务工作者控制');
});
// 检查当前状态
console.log("当前 Service Worker 状态:", navigator.serviceWorker.controller ? "已控制" : "未控制");
let reg = navigator.serviceWorker.getRegistration();
console.log("注册的 Service Worker:", reg);
updatefound
-
触发条件:检测到新的服务工作者脚本并开始安装
-
特点:仅表示发现新版本,不代表已激活
-
示例使用:
javascript
registration.addEventListener('updatefound', () => {
console.log('发现新版本服务工作者');
});
activate
-
触发条件:服务工作者成功激活,获得控制权
-
特点:这是清理旧资源的理想时机(但还不能控制现有客户端)
-
示例使用:
javascript
self.addEventListener('activate', event => {
// 清理旧缓存
event.waitUntil(caches.delete('old-cache'));
});
生命周期顺序
-
检测到新版本 → updatefound 事件
-
安装完成后等待激活 → (无特定事件)
-
激活成功 → activate 事件(服务工作者已就绪)
-
控制页面 → controllerchange 事件(页面已被控制)
重要区别
-
activate 在服务工作者线程内监听
-
updatefound 、controllerchange 在客户端页面(主线程)中监听
-
updatefound 表示"发现更新",activate 表示"准备就绪",controllerchange 表示"已接管控制"
为什么 updatefound 在主线程监听?
逻辑原因:
发现更新是客户端的责任:浏览器/页面需要知道何时有更新可用
与用户交互相关:通常需要向用户显示更新提示
安装过程在后台:SW安装是独立过程,页面需要被通知
记忆要点
-
主线程监听:
-
updatefound(发现更新) -
controllerchange(控制权变更) -
statechange(状态变化)
-
-
服务线程监听:
-
install(自身安装) -
activate(自身激活) -
fetch/push/sync(功能事件)
-
-
简单规则:
-
关于"SW与页面关系"的事件 → 在主线程
-
关于"SW自身状态"的事件 → 在服务线程
-
"是否立即生效" 详细解释
立即生效的情况
javascript
// 示例1: 立即生效的属性
if (registration.waiting) { // 立即返回true/false
// 已有等待中的SW
}
// 示例2: 立即触发的事件
navigator.serviceWorker.addEventListener('controllerchange', () => {
// 事件触发后立即执行
window.location.reload(); // 可以立即重新加载页面
});
需等待 Promise 的情况
javascript
// 示例1: 异步操作
navigator.serviceWorker.ready.then(() => {
// 只有SW完全就绪后才会执行
console.log('SW准备就绪');
});
// 示例2: 需要 waitUntil() 的事件
self.addEventListener('install', event => {
// 必须等待缓存操作完成
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll(['/app.js', '/style.css']); // 异步操作
})
);
// 只有在Promise完成后,install才完成
});
需等待 respondWith() 的情况
javascript
self.addEventListener('fetch', event => {
// 必须调用 respondWith() 并提供Promise
event.respondWith(
caches.match(event.request).then(response => {
// 这个Promise完成前,页面请求不会得到响应
return response || fetch(event.request);
})
);
});
不同生效时机的使用场景
立即生效 → 用户界面响应
javascript
// 立即更新UI状态
registration.addEventListener('updatefound', () => {
// 立即显示更新提示
showUpdateToast('发现新版本,正在下载...');
});
需等待 Promise → 确保操作完成
javascript
// 确保SW激活后再发送消息
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage('SW已就绪,开始工作');
});
需 waitUntil → 保证生命周期完整性
javascript
self.addEventListener('activate', event => {
// 保证清理完成前,SW不会进入下一步
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== 'current-v2') {
return caches.delete(cacheName); // 删除旧缓存
}
})
);
})
);
});
关键要点总结
-
立即生效:主要用于状态查询和UI更新,不涉及异步操作
-
等待 Promise:用于确保操作顺序,避免竞态条件
-
waitUntil/respondWith:服务线程生命周期管理的关键机制,保证异步操作完成
-
实际影响:是否立即生效直接影响用户体验和代码执行逻辑