Service Worker 生命周期:注册、安装、激活、更新、作用域控制、调试技巧

Service Worker 是离线优先应用的核心引擎,它的生命周期与普通网页脚本完全不同。理解并掌控这一生命周期,是构建可靠离线体验的前提。本章从注册到更新,逐一拆解每个阶段的原理、实现方法与调试要点。

生命周期总览

Service Worker 的生命周期包含五个主要阶段:注册→安装→激活→运行→更新。每个阶段都是异步事件驱动的,开发者通过事件监听(install、activate、fetch、message等)介入控制。
Service Worker 生命周期流程 ① 注册 navigator.serviceWorker.register() ② 安装 install 事件 / 预缓存 ③ 激活 activate 事件 / 旧缓存清理 ④ 控制页面 scope 决定拦截范围 ⑤ 运行 & 更新 fetch / 版本检测 新版本触发更新 更新后需等待页面关闭再打开 新的 Service Worker 才生效

阶段一:注册(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);
  });
}
参数说明默认值
scriptURLService 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 的更新机制是其独特之处。当浏览器检测到脚本有字节差异时,会触发新的安装流程,同时旧版本继续运行直到所有页面关闭。

触发更新的场景:

  1. 用户在打开的页面中浏览器每隔 24 小时自动检查一次。
  2. 调用 registration.update() 强制检查。
  3. 页面跳转到新的 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() 上报安装/激活状态到日志服务。