Skip to content

Service Worker ​

Service Worker 是浏览器在后台独立运行的脚本,提供离线缓存、消息推送、后台同步等能力。

生命周期 ​

注册 → 安装 → 激活 → 运行 → 更新
              ↑
          控制页面
注册 → 安装 → 激活 → 运行 → 更新
              ↑
          控制页面
navigator.serviceWorker.register('/sw.js')
                  │
                  ▼
              installing
                  │
                  ▼
              installed
                  │  (waiting until no active worker)
                  ▼
              activating
                  │
                  ▼
              activated
                  │  (controlling pages)
                  ▼
              redundant (被新版本替换)
navigator.serviceWorker.register('/sw.js')
                  │
                  ▼
              installing
                  │
                  ▼
              installed
                  │  (waiting until no active worker)
                  ▼
              activating
                  │
                  ▼
              activated
                  │  (controlling pages)
                  ▼
              redundant (被新版本替换)

注册 ​

js
// main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/',
      })
      console.log('SW registered:', registration.scope)
    } catch (e) {
      console.error('SW registration failed:', e)
    }
  })
}
// main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/',
      })
      console.log('SW registered:', registration.scope)
    } catch (e) {
      console.error('SW registration failed:', e)
    }
  })
}

安装事件 ​

js
// sw.js
const CACHE_NAME = 'my-app-v1'
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.png',
]

self.addEventListener('install', (event) => {
  // 跳过等待,立即激活
  self.skipWaiting()

  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(PRECACHE_URLS)
    })
  )
})
// sw.js
const CACHE_NAME = 'my-app-v1'
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.png',
]

self.addEventListener('install', (event) => {
  // 跳过等待,立即激活
  self.skipWaiting()

  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(PRECACHE_URLS)
    })
  )
})

激活事件 ​

js
self.addEventListener('activate', (event) => {
  // 清理旧缓存
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      )
    })
  )

  // 立即控制所有页面
  self.clients.claim()
})
self.addEventListener('activate', (event) => {
  // 清理旧缓存
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      )
    })
  )

  // 立即控制所有页面
  self.clients.claim()
})

拦截请求 ​

js
self.addEventListener('fetch', (event) => {
  // 只处理 GET 请求
  if (event.request.method !== 'GET') return

  event.respondWith(
    caches.match(event.request).then((cached) => {
      // 缓存优先,网络回退
      return cached || fetch(event.request)
    })
  )
})
self.addEventListener('fetch', (event) => {
  // 只处理 GET 请求
  if (event.request.method !== 'GET') return

  event.respondWith(
    caches.match(event.request).then((cached) => {
      // 缓存优先,网络回退
      return cached || fetch(event.request)
    })
  )
})

更新 Service Worker ​

js
// sw.js — 改变文件内容即触发更新
self.addEventListener('install', (event) => {
  // 新版本安装成功后跳过等待
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  // 接管页面后通知页面刷新
  event.waitUntil(
    self.clients.matchAll().then((clients) => {
      clients.forEach((client) => {
        client.postMessage({ type: 'SW_UPDATED' })
      })
    })
  )
})

// main.js — 监听更新
navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data.type === 'SW_UPDATED') {
    if (confirm('新版本可用,是否刷新?')) {
      window.location.reload()
    }
  }
})

// 监听 registration 更新
registration.addEventListener('updatefound', () => {
  const newWorker = registration.installing
  newWorker.addEventListener('statechange', () => {
    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
      // 有更新可用
    }
  })
})
// sw.js — 改变文件内容即触发更新
self.addEventListener('install', (event) => {
  // 新版本安装成功后跳过等待
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  // 接管页面后通知页面刷新
  event.waitUntil(
    self.clients.matchAll().then((clients) => {
      clients.forEach((client) => {
        client.postMessage({ type: 'SW_UPDATED' })
      })
    })
  )
})

// main.js — 监听更新
navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data.type === 'SW_UPDATED') {
    if (confirm('新版本可用,是否刷新?')) {
      window.location.reload()
    }
  }
})

// 监听 registration 更新
registration.addEventListener('updatefound', () => {
  const newWorker = registration.installing
  newWorker.addEventListener('statechange', () => {
    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
      // 有更新可用
    }
  })
})

开发调试 ​

Chrome DevTools → Application → Service Workers

  • Update on reload — 每次刷新时更新 SW
  • Offline — 模拟离线
  • Bypass for network — 绕过 SW
  • Unregister — 注销 SW

SW 的限制 ​

限制说明
HTTPS 要求本地 localhost 可以,线上必须 HTTPS
不能操作 DOMSW 没有 window/document,只能通过 postMessage 通信
生命周期不活动时会被浏览器终止,下次有事件时重新启动
存储限制缓存存储有限,需及时清理