← 返回题目列表

Service Worker 的生命周期、离线缓存和更新机制是怎样的?

高频 困难 第 17 / 30 题 更新于 2026/07/29
浏览器原理Service WorkerPWA离线缓存

简化版

Service Worker 是运行在页面之外的后台脚本,像浏览器、页面和网络之间的可编程代理,可以拦截请求、管理缓存、支持离线访问、推送和后台同步。它有注册、安装、激活、控制页面、更新等生命周期,常见坑是缓存版本管理和新旧 SW 并存。

详细版

基本流程:

register -> install -> activate -> fetch events -> update

常见用途:

  • 静态资源预缓存。
  • 请求拦截和运行时缓存。
  • 离线兜底页面。
  • PWA 安装体验。
  • Push 和 Background Sync 等能力。

Service Worker 不能直接操作 DOM,和页面通过 postMessage 通信。它通常要求 HTTPS 环境。更新时,新 worker 安装后可能处于 waiting,直到旧页面关闭或主动 skipWaitingclients.claim 才接管。

完整版教学

一、Service Worker 是网络代理,不是普通 Worker

Service Worker 运行在页面之外,可以在页面和网络之间拦截请求。它不是用来做普通 CPU 计算的 Worker,而是偏向缓存、离线、推送和后台能力。

页面 fetch('/app.js')
  -> Service Worker fetch 事件
  -> Cache Storage 或 Network
  -> 返回 Response

它能让网页在弱网、断网或重复访问时更稳定。比如第一次访问缓存了核心 JS/CSS,第二次即使网络慢,也能先从缓存返回应用外壳。

面试时要把 Web Worker 和 Service Worker 区分开:前者偏后台计算,后者偏网络拦截和离线能力。

二、注册、安装、激活是生命周期主线

页面通过 navigator.serviceWorker.register() 注册 SW 文件。浏览器下载并解析成功后,触发 install;安装完成后进入 activate;激活后才能控制页面并处理 fetch。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
}

Service Worker 文件:

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('app-v1').then(cache => cache.addAll(['/offline.html']))
  )
})

self.addEventListener('activate', (event) => {
  event.waitUntil(cleanOldCaches())
})
register -> install -> waiting -> activate -> controlling clients

install 适合预缓存核心资源,activate 适合清理旧缓存和迁移状态。不要把大量不可控请求都塞进 install,否则安装失败会导致 SW 不可用。

三、fetch 事件决定缓存策略

Service Worker 的核心能力是监听 fetch。不同资源适合不同缓存策略:HTML 通常要网络优先或短缓存,带 hash 的静态资源可以缓存优先,接口数据要按业务处理。

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request)
    })
  )
})
策略适合资源风险
Cache Firsthash 静态资源、字体、图片旧资源清理
Network FirstHTML、关键接口弱网等待较久
Stale While Revalidate列表、非关键资源短暂看到旧数据
Offline Fallback离线页面内容可能不完整

数字例子:一个 300KB 的主 JS 使用 hash 文件名,可以长期 Cache First;而 /index.html 如果长期缓存,用户可能一直加载旧资源入口。

四、更新机制的难点是新旧版本并存

浏览器发现 SW 文件内容变化,会安装新的 Service Worker。但如果旧页面仍打开,新 SW 可能进入 waiting 状态,不会立刻控制页面。

旧 SW 控制页面 A
新 SW 下载并 install
  -> waiting
页面 A 关闭或主动跳过等待
  -> 新 SW activate

这能避免一个页面运行期间资源版本突然混用。但工程上也会带来“发布了用户没立即更新”的体验。

可以使用 self.skipWaiting() 让新 SW 尽快激活,再用 clients.claim() 接管页面。但这可能导致当前页面 JS 和缓存资源版本不匹配,所以要谨慎。

易错点:Service Worker 更新不是刷新 CDN 那么简单,页面、SW、Cache Storage 三者都可能处在不同版本。

五、缓存版本管理是上线稳定性的关键

SW 常用缓存名带版本,例如 app-v1app-v2。新版本激活时清理旧缓存,避免资源无限堆积和旧文件误用。

const CACHE_NAME = 'app-v2'

async function cleanOldCaches() {
  const keys = await caches.keys()
  return Promise.all(
    keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))
  )
}

如果每次发布都新增 5MB 缓存,10 次发布就是 50MB。移动端存储紧张时,浏览器可能自动清理缓存,用户离线体验就变得不可预测。

同时要避免缓存不透明。线上问题排查时,用户可能不是请求到了服务器旧文件,而是 SW 返回了旧缓存。排查要看 DevTools 的 Application 面板和 Network 是否来自 Service Worker。

六、安全边界和适用场景

Service Worker 通常要求安全上下文,也就是 HTTPS 或本地开发环境。因为它能拦截请求,如果被中间人篡改,危害很大。

如果 SW 被恶意替换:
  -> 可伪造离线内容
  -> 可缓存恶意资源
  -> 可劫持同作用域请求

作用域也很重要。注册在 /sw.js 的 SW 通常能控制根路径下页面;注册在 /app/sw.js 的作用域更窄。作用域越大,影响越大,设计时要明确。

适合使用 SW 的场景包括文档站、后台系统应用壳、弱网业务、PWA、离线阅读、静态资源缓存。不适合对实时性强且缓存策略复杂的核心交易数据无脑缓存。

七、常见误区与追问

  • 误区:Service Worker 可以直接操作 DOM。 它运行在页面外,不能直接访问 DOM,需要通过消息与页面通信。
  • 误区:发布新 SW 后用户立刻使用新版本。 新 SW 可能 waiting,直到旧页面关闭或主动跳过等待。
  • 误区:所有请求都 Cache First 最快。 HTML 和接口无脑缓存会造成旧页面、旧数据和业务错误。
  • 追问:skipWaiting 有什么风险? 新 SW 立即接管可能让当前页面和缓存资源版本不匹配。
  • 追问:如何排查 SW 缓存问题? 看 DevTools Application、Cache Storage、Network 中是否 from ServiceWorker,并尝试 unregister。
  • 追问:SW 为什么要求 HTTPS? 它能拦截请求,必须防止传输过程中被篡改。

八、加强记忆

Service Worker 按“代理、生命周期、策略、更新”记:它是页面和网络之间的代理;生命周期有 install、activate、fetch;缓存策略要按资源分类;更新时新旧版本会并存。把 Cache Storage 和 SW 版本一起管理,离线能力才可靠。