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 |
| 不能操作 DOM | SW 没有 window/document,只能通过 postMessage 通信 |
| 生命周期 | 不活动时会被浏览器终止,下次有事件时重新启动 |
| 存储限制 | 缓存存储有限,需及时清理 |
Cao Note