Web Worker 与 Service Worker 详解
Category(分类): HTML Status: 未知
本文对原文进行了重新整理和勘误。示例代码使用现代 Web Worker、Service Worker 和 Vite/Webpack 兼容的写法,删除了网页复制残留、失效配置和不准确的生命周期描述。
目录
- 一、为什么需要 Worker
- 二、Web Worker 与 Service Worker 的关系
- 三、Web Worker
- 四、Service Worker
- 五、常见缓存策略
- 六、使用 Workbox
- 七、Web Worker 与 Service Worker 对比
- 八、常见问题和调试建议
- 九、总结
一、为什么需要 Worker
浏览器页面通常由主线程负责多项工作,包括:
- 执行页面 JavaScript;
- 处理用户输入和事件;
- 计算样式和布局;
- 更新 DOM;
- 绘制页面。
如果主线程长时间执行排序、加密、压缩、图像处理等计算,就可能无法及时处理输入和绘制任务,页面会出现卡顿。将适合的计算任务交给 Worker,可以让页面主线程有更多时间处理交互和渲染。
需要注意两点:
- JavaScript 通常是“每个执行上下文单线程”,并不代表整个浏览器只有一个 JavaScript 线程;
- Worker 不能消除计算成本,只是将计算转移到其他执行上下文。创建 Worker、传输数据和调度线程本身也有成本,小任务不一定适合使用 Worker。
二、Web Worker 与 Service Worker 的关系
Web Worker 和 Service Worker 都属于 Worker 类型的执行环境,都不能直接访问页面 DOM,但设计目标不同:
| 对比项 | Web Worker | Service Worker |
|---|---|---|
| 主要目的 | 把计算任务移出页面主线程 | 代理网络请求、实现缓存和离线能力 |
| 启动方式 | 页面主动调用 new Worker() | 页面调用 navigator.serviceWorker.register() 注册 |
| 作用范围 | 通常只服务于创建它的页面 | 可以控制作用域内的多个页面 |
| 生命周期 | 通常与创建它的页面相关 | 由浏览器按事件启动、暂停和终止 |
| 是否能访问 DOM | 不能 | 不能 |
| 是否能拦截页面请求 | 不能作为通用网络代理 | 可以拦截受控页面作用域内的 Fetch 请求 |
| 常见 API | postMessage()、terminate()、importScripts() | install、activate、fetch、push、sync |
| 常见用途 | 大量计算、图像处理、数据分析 | 离线缓存、PWA、通知和后台同步 |
Service Worker 可以看作一种具有特殊生命周期和事件模型的 Worker,但它不是 Web Worker 的简单替代品,也不是一个始终运行的后台线程。
三、Web Worker
3.1 什么是 Web Worker
Web Worker 是浏览器提供的后台 JavaScript 执行环境。浏览器通常会为它安排独立线程,但具体线程调度属于浏览器实现细节。
Dedicated Worker(专用 Worker)只能被创建它的页面或 Worker 访问。它适合执行与页面相关、但不需要直接操作 DOM 的计算任务。
Worker 创建后不会因为主线程继续执行而自动停止,但它也不是永久运行的进程:
- 主线程可以调用
terminate()立即终止它; - Worker 自身可以调用
self.close()结束自己; - 页面关闭后,关联的 Worker 通常也会结束;
- 浏览器可能对后台页面进行冻结或资源回收。
3.2 Web Worker 的限制
不能直接操作 DOM
Worker 中不能访问 window、document 和页面 DOM,也不能直接修改页面元素:
// Worker 中不可用
window.document.querySelector('#app')
正确方式是由 Worker 计算数据,再通过消息发送给页面,由页面更新 DOM。
不能使用大部分 Window API
Worker 没有页面的 window 对象,但可以使用部分专用全局对象和 API,例如:
self;navigator;location(表示 Worker 脚本地址);fetch();XMLHttpRequest(经典 Worker 中可用,但通常优先使用fetch());WebSocket;IndexedDB;Web Crypto。
alert()、confirm() 等依赖页面窗口的 API 不能在 Worker 中使用。
Worker 脚本需要满足 URL 和安全策略
直接使用 new Worker('worker.js') 时,脚本通常需要与创建它的页面同源。实际项目中还可能使用构建工具生成的资源、blob: URL 或模块 Worker。
不要直接依赖 file:// 打开 HTML 文件来测试 Worker。应通过本地 HTTP 服务器运行项目。
不能依赖普通全局变量保存持久状态
Worker 的全局变量只在当前 Worker 实例存活期间有效。需要持久保存的数据应放在页面端、IndexedDB 或其他合适的存储中。
主线程和 Worker 之间不能直接共享普通 JavaScript 对象
通信需要通过 postMessage(),数据默认使用结构化克隆算法复制。函数、DOM 节点等对象不能直接传递。
3.3 创建和通信
主线程代码
if (typeof Worker !== 'undefined') {
const worker = new Worker('./worker.js')
worker.postMessage({
type: 'CALCULATE',
numbers: [1, 2, 3, 4, 5]
})
worker.addEventListener('message', (event) => {
console.log('收到 Worker 结果:', event.data)
})
worker.addEventListener('error', (event) => {
console.error('Worker 发生错误:', event.message)
})
}
worker.js
self.addEventListener('message', (event) => {
const { type, numbers } = event.data || {}
if (type !== 'CALCULATE' || !Array.isArray(numbers)) {
return
}
const result = numbers.reduce((sum, value) => sum + value, 0)
self.postMessage({
type: 'RESULT',
result
})
})
也可以使用属性形式监听事件:
self.onmessage = (event) => {
self.postMessage(`收到:${event.data}`)
}
页面和 Worker 两端都可以使用 addEventListener('message', ...) 或 onmessage。推荐统一约定消息格式,例如使用 type 字段区分不同命令。
3.4 终止和错误处理
主线程终止 Worker
const worker = new Worker('./worker.js')
// terminate() 会立即终止 Worker,不会等待当前任务完成
worker.terminate()
Worker 终止自身
self.close()
terminate() 和 close() 都会停止 Worker,但不会自动保存未完成的任务。需要保证数据完整性时,应在终止前设计明确的完成、取消和错误协议。
监听错误
const worker = new Worker('./worker.js')
worker.addEventListener('error', (event) => {
console.error('Worker 错误:', event.message)
})
worker.addEventListener('messageerror', (event) => {
console.error('Worker 消息无法反序列化:', event)
})
Worker 内部也可以监听错误,但很多错误会通过 Worker 实例的 error 事件传递给主线程,因此主线程应始终设置错误处理逻辑。
3.5 结构化克隆与可转移对象
postMessage() 默认使用结构化克隆算法传递数据。例如对象和数组可以直接发送:
worker.postMessage({
name: 'demo',
values: [1, 2, 3]
})
大数据复制可能消耗时间和内存。对于 ArrayBuffer、MessagePort、ImageBitmap 等可转移对象,可以使用第二个参数转移所有权,避免复制:
const buffer = new ArrayBuffer(1024)
worker.postMessage(buffer, [buffer])
// buffer 的所有权已经转移,主线程不能再使用它
转移对象后,发送方不再拥有该对象。使用 Transferable 时要确保后续代码不会继续访问已经转移的对象。
3.6 使用模块 Worker
现代项目通常推荐模块 Worker。模块 Worker 可以使用 import,并支持更清晰的模块组织。
主线程
const worker = new Worker(
new URL('./math.worker.js', import.meta.url),
{ type: 'module' }
)
worker.postMessage({
numbers: [10, 20, 30]
})
worker.onmessage = ({ data }) => {
console.log('计算结果:', data)
}
math.worker.js
import { sum } from './math.js'
self.onmessage = ({ data }) => {
self.postMessage(sum(data.numbers))
}
// math.js
export function sum(numbers) {
return numbers.reduce((total, value) => total + value, 0)
}
importScripts() 只适用于经典 Worker,不适用于模块 Worker:
// Classic Worker 中可以使用
importScripts('./helper.js')
// Module Worker 中应使用 import
import { helper } from './helper.js'
3.7 在 Vite、Vue 和 Nuxt 中使用
现代 Vite、Webpack 5 和 Nuxt 项目一般不需要安装 worker-loader,可以直接使用:
const worker = new Worker(
new URL('./workers/task.worker.js', import.meta.url),
{ type: 'module' }
)
Vite 也支持 ?worker 方式:
import TaskWorker from './workers/task.worker.js?worker'
const worker = new TaskWorker()
在 Vue 组件中,Worker 应在客户端创建,并在组件卸载时终止:
<script setup>
import { onBeforeUnmount, onMounted } from 'vue'
let worker
onMounted(() => {
worker = new Worker(
new URL('./workers/task.worker.js', import.meta.url),
{ type: 'module' }
)
worker.postMessage({ type: 'START' })
})
onBeforeUnmount(() => {
worker?.terminate()
})
</script>
在 Nuxt 等 SSR 框架中,window、Worker 等浏览器 API 不能在服务端执行。可以使用 onMounted()、客户端插件或 import.meta.client 确保代码只在浏览器运行。
3.8 Web Worker 的适用场景
适合使用 Web Worker 的场景包括:
- 大量数据的排序、过滤、聚合和统计;
- 加密、解密、哈希和压缩;
- 复杂的文本解析、CSV/JSON 处理;
- 图像像素处理和部分音视频计算;
- 复杂算法、路径规划和数据预计算;
- 使用
OffscreenCanvas进行支持的 Canvas 绘制任务。
需要注意:
- Worker 不能直接操作普通 DOM;
- 普通 Canvas 不能直接交给 Worker 使用,通常需要
OffscreenCanvas; - Worker 不能直接创建页面中的
<img>元素,但可以请求图像数据或创建ImageBitmap; - 轻量计算不一定值得创建 Worker,应考虑数据传输和启动成本。
四、Service Worker
4.1 什么是 Service Worker
Service Worker 是一种事件驱动的 Worker。它位于 Web 应用、浏览器和网络之间,可以在页面处于其控制范围且满足条件时处理网络请求。
它主要用于:
- 预缓存应用壳和静态资源;
- 根据策略从缓存或网络返回响应;
- 提供离线页面和弱网体验;
- 接收 Push 事件并显示通知;
- 配合 Background Sync 重试短时网络任务。
Service Worker 不是永久运行的后台进程,也不是普通 Web Worker 的“常驻版本”。浏览器可以终止空闲的 Service Worker,需要处理事件时再重新启动它。
4.2 Service Worker 的能力和限制
Service Worker 可以访问:
self;caches;fetch();clients;registration;indexedDB;push、sync、notificationclick等事件。
Service Worker 不能直接访问:
window;document;- 页面 DOM;
- 页面当前 URL 对应的
window.location。
Service Worker 中的 location 是 Worker 脚本本身的地址。如果需要查看或操作页面,应使用 clients.matchAll()、postMessage() 等机制与页面通信。
Service Worker 可以拦截请求,但不是任意网络请求的全局代理。只有被当前 Service Worker 控制、并且位于注册作用域内的页面请求,才会触发对应的 fetch 事件。
4.3 安全上下文和作用域
正式环境通常必须使用 HTTPS。本地开发时,http://localhost 通常被浏览器视为安全上下文。
Service Worker 的默认作用域是脚本所在目录及其子目录:
| 脚本地址 | 默认作用域 |
|---|---|
/sw.js | / |
/app/sw.js | /app/ |
/static/sw.js | /static/ |
下面的注册方式只能让脚本控制 /app/:
navigator.serviceWorker.register('/app/sw.js')
如果要让位于子目录的脚本控制更上层的路径,服务端需要返回:
Service-Worker-Allowed: /
作用域越大,Service Worker 能拦截的请求越多,应避免不必要地设置过大的作用域。
4.4 注册 Service Worker
页面端注册:
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
})
console.log('Service Worker 注册成功:', registration.scope)
} catch (error) {
console.error('Service Worker 注册失败:', error)
}
})
}
register() 返回一个 Promise。注册成功后,浏览器还要完成安装和激活,当前页面也不一定马上受其控制。
等待一个已经激活的注册对象:
const registration = await navigator.serviceWorker.ready
console.log('Service Worker 已准备就绪:', registration.scope)
注册路径应保持稳定。更新 Service Worker 时,通常保持 /sw.js 不变,让浏览器重新检查脚本内容,而不是不断改成 /sw-v2.js、/sw-v3.js。
4.5 生命周期
Service Worker 的生命周期状态通常为:
installing
↓
installed / waiting
↓
activating
↓
activated
↓
redundant

fetch、push、sync 和 message 是激活后的功能事件,不是生命周期状态。
installing
浏览器获取到新脚本后创建新的 Worker 实例,并触发 install。通常在这里执行预缓存。
waiting
如果旧版本仍控制着页面,新版本安装成功后通常会处于 waiting 状态,等待旧版本不再控制客户端。
activating
旧版本退出控制后,新版本进入激活阶段。通常在这里删除旧缓存、迁移数据或执行初始化任务。
activated
激活后的 Service Worker 可以处理被控制页面的 fetch、message、push 等事件。
redundant
安装失败或被更新版本替代的 Worker 会进入 redundant 状态。
Service Worker 实例可以在空闲时被浏览器终止,但这不会自动删除它的注册信息或缓存数据。缓存数据仍可能被用户清理或浏览器因存储压力回收。
4.6 安装与预缓存
在 sw.js 中预缓存应用壳和离线页面:
const APP_PREFIX = 'demo-app-'
const STATIC_CACHE = `${APP_PREFIX}static-v1`
const PRECACHE_URLS = [
'/',
'/offline.html',
'/app.js',
'/styles.css'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => cache.addAll(PRECACHE_URLS))
)
})
event.waitUntil() 会延长 install 事件的生命周期。如果 Promise rejected,安装通常会失败,新版本不会进入可用状态。
cache.addAll() 中只要有一个资源请求失败,整个操作就可能失败。因此预缓存列表应只包含稳定存在、确实需要离线使用的资源。
不建议在安装阶段缓存大量接口数据或所有图片。变化频繁的内容应在运行时根据请求逐步缓存。
4.7 激活与清理旧缓存
const APP_PREFIX = 'demo-app-'
const STATIC_CACHE = `${APP_PREFIX}static-v1`
const RUNTIME_CACHE = `${APP_PREFIX}runtime-v1`
const CURRENT_CACHES = new Set([
STATIC_CACHE,
RUNTIME_CACHE
])
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
const cacheNames = await caches.keys()
await Promise.all(
cacheNames
.filter((name) =>
name.startsWith(APP_PREFIX) && !CURRENT_CACHES.has(name)
)
.map((name) => caches.delete(name))
)
})())
})
删除缓存时不要无条件执行 caches.keys().then(keys => keys.map(caches.delete)),因为同一源下可能有多个应用或多个功能使用 Cache Storage。应使用应用专属前缀。
4.8 拦截请求
一个基础的运行时缓存示例:
self.addEventListener('fetch', (event) => {
const request = event.request
const url = new URL(request.url)
// Cache Storage 通常只处理 GET 请求
if (request.method !== 'GET') {
return
}
// 当前示例只缓存同源资源
if (url.origin !== self.location.origin) {
return
}
event.respondWith((async () => {
const cache = await caches.open(RUNTIME_CACHE)
const cachedResponse = await cache.match(request)
if (cachedResponse) {
return cachedResponse
}
const networkResponse = await fetch(request)
if (networkResponse.ok) {
event.waitUntil(
cache.put(request, networkResponse.clone())
)
}
return networkResponse
})())
})
需要注意:
respondWith()必须提供 Response 或 Promise;- Request 或 Response 被读取后通常需要使用
clone(); - Cache Storage 不适合直接缓存 POST、PUT 等请求;
fetch()收到 HTTP 404、500 时通常不会 reject,是否回退缓存需要自行判断response.ok;- 跨域响应涉及 CORS 和 opaque response,不应简单套用同源缓存逻辑;
event.waitUntil()用于保证后台缓存写入等异步任务有机会完成。
页面导航通常需要单独使用 Network First,并提供离线页面:
self.addEventListener('fetch', (event) => {
if (event.request.mode !== 'navigate') {
return
}
event.respondWith((async () => {
try {
const response = await fetch(event.request)
if (response.ok) {
const cache = await caches.open(RUNTIME_CACHE)
event.waitUntil(cache.put(event.request, response.clone()))
}
return response
} catch {
const cache = await caches.open(STATIC_CACHE)
return (await cache.match('/offline.html')) || Response.error()
}
})())
})
4.9 更新策略
浏览器会在注册、导航和部分功能事件等时机检查 Service Worker 脚本是否更新。脚本 URL 不变但文件内容发生变化时,也可以安装新版本。
更新过程通常是:
旧版本 activated
↓ 检查到 sw.js 内容变化
新版本 install
↓
新版本 waiting
↓ 旧版本不再控制客户端
新版本 activate
skipWaiting()
self.addEventListener('install', () => {
self.skipWaiting()
})
skipWaiting() 会让新版本尽快跳过 waiting,但可能造成旧页面代码、新 Service Worker 和新缓存混合使用。只有确认版本兼容时才应使用。
clients.claim()
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
clients.claim() 会让已激活的 Service Worker 接管当前作用域内尚未受控的页面。它不会更新缓存,也不能代替版本兼容性设计。
主动检查更新
const registration = await navigator.serviceWorker.ready
await registration.update()
也可以通过 registration.updatefound 和 Worker 的 statechange 监听更新过程,并在发现新版本后提示用户刷新。
4.10 页面与 Service Worker 通信
页面发送消息:
navigator.serviceWorker.controller?.postMessage({
type: 'CLEAR_RUNTIME_CACHE'
})
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('来自 Service Worker:', event.data)
})
Service Worker 接收消息:
self.addEventListener('message', async (event) => {
if (event.data?.type !== 'CLEAR_RUNTIME_CACHE') {
return
}
await caches.delete(RUNTIME_CACHE)
event.source?.postMessage({
type: 'RUNTIME_CACHE_CLEARED'
})
})
Service Worker 不能直接操作页面 DOM。需要改变页面内容时,应将数据发送回页面,由页面脚本执行 DOM 操作。
4.11 Push 与通知
Notification API 和 Push API 是相关但不同的能力:
- Notification API:创建和显示通知;
- Push API:接收推送消息;
- Service Worker:在后台接收
push事件并调用showNotification()。
请求通知权限应尽量放在用户点击按钮等用户手势中:
async function requestNotificationPermission() {
if (!('Notification' in window)) {
return false
}
const permission = await Notification.requestPermission()
return permission === 'granted'
}
Service Worker 中处理 Push:
self.addEventListener('push', (event) => {
const data = event.data?.json() || {
title: '新消息',
body: '你收到了一条新消息'
}
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png'
})
)
})
真正的 Web Push 还需要:
- 页面创建 Push subscription;
- 将订阅信息发送到后端;
- 后端使用 VAPID 等方式通过 Push 服务发送消息;
- 浏览器唤醒 Service Worker 并触发
push事件。
通知点击处理:
self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil((async () => {
const clients = await self.clients.matchAll({
type: 'window',
includeUncontrolled: true
})
const client = clients.find((item) => item.url === self.registration.scope)
if (client) {
await client.focus()
} else {
await self.clients.openWindow(self.registration.scope)
}
})())
})
推送和通知都受浏览器权限、操作系统设置、浏览器策略和网络环境影响,不能保证一定送达或立即显示。
4.12 Background Sync
Background Sync 可以将短时、可重试的任务延迟到网络恢复后执行,例如发送离线消息队列。
页面端注册任务:
async function registerOutboxSync() {
const registration = await navigator.serviceWorker.ready
if (!('sync' in registration)) {
// 降级:在下次打开页面时主动重试
return false
}
await registration.sync.register('send-outbox')
return true
}
Service Worker 端处理:
self.addEventListener('sync', (event) => {
if (event.tag === 'send-outbox') {
event.waitUntil(flushOutbox())
}
})
async function flushOutbox() {
// 从 IndexedDB 读取待发送数据
// 发送成功后删除队列数据
// 发送失败时抛出异常,以便后续重试
}
Background Sync 不是所有浏览器都支持,也不保证网络恢复后立即执行。它适合短任务,不适合大文件下载或必须严格按时完成的任务。应用必须提供普通页面重试等降级方案。
五、常见缓存策略
| 策略 | 处理流程 | 适合场景 |
|---|---|---|
| Cache Only | 只从缓存读取 | 已预缓存的应用壳 |
| Network Only | 只从网络读取 | 登录、支付、强实时接口 |
| Cache First | 先缓存,未命中再访问网络 | 带版本指纹的静态资源、图片、字体 |
| Network First | 先访问网络,失败后读取缓存 | HTML、文章、需要较新数据的内容 |
| Stale While Revalidate | 立即返回缓存,同时请求网络更新 | 头像、图片、非关键内容 |
| Network Race | 缓存和网络竞争 | 特定性能场景,需要自行处理失败情况 |
“渐进式缓存”通常是运行时逐步缓存资源的开发模式,不是浏览器内置的独立缓存策略。“Network Race”也不是 Service Worker 的内置策略。
选择策略时应考虑:
- 内容是否允许过期;
- 是否必须支持离线;
- 请求是否包含用户私密数据;
- 资源是否有文件指纹;
- 网络错误和 HTTP 错误如何处理;
- 缓存未命中时返回什么降级内容。
六、使用 Workbox
Workbox 是用于构建 Service Worker 的工具库,提供预缓存、运行时路由、缓存过期和常用策略实现。
现代项目应使用当前版本的 Workbox,并通过 npm 和构建工具集成,不建议继续复制旧版 workbox-sw.js CDN 示例或安装很早期的 worker-loader 方案。
模块化 Workbox 示例:
import { registerRoute } from 'workbox-routing'
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images-v1',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 7 * 24 * 60 * 60
})
]
})
)
registerRoute(
({ request }) => request.mode === 'navigate',
new NetworkFirst({
cacheName: 'pages-v1',
networkTimeoutSeconds: 3
})
)
registerRoute(
({ request }) =>
request.destination === 'style' || request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'assets-v1'
})
)
networkTimeoutSeconds 是正确的配置名,不能写成 networkTimetoutSeconds。Workbox 仍然需要根据资源类型配置路由,不能对所有请求使用同一种策略。
七、Web Worker 与 Service Worker 对比
可以从下面几个问题快速判断应该使用哪一种:
需要处理大量计算,但页面仍需要保持流畅
使用 Web Worker:
const worker = new Worker(
new URL('./calculate.worker.js', import.meta.url),
{ type: 'module' }
)
需要拦截请求、实现离线页面
使用 Service Worker:
navigator.serviceWorker.register('/sw.js')
需要在多个标签页之间共享计算任务
可以考虑 Shared Worker,但需要检查浏览器支持情况和业务复杂度。Shared Worker 与 Service Worker 的生命周期、通信方式和用途都不同,不能直接互换。
需要后台推送通知
使用 Push API + Service Worker,并准备后端推送服务和权限降级方案。
八、常见问题和调试建议
1. 为什么 Worker 代码不能访问 window
Worker 没有页面 Window 全局对象。应使用 self,并通过消息让页面执行 DOM 操作。
2. 为什么 Service Worker 注册成功但 fetch 没有触发
常见原因包括:
- 当前页面不在 Service Worker 作用域内;
- 页面尚未被该 Worker 控制,首次注册后没有刷新;
- 脚本返回了 HTML 错误页而不是 JavaScript;
- 页面不是 HTTPS 或 localhost;
- 旧 Service Worker 仍处于控制状态;
- 请求被代码中的方法、来源或路径过滤条件排除了。
3. 为什么缓存没有永久保存
Cache Storage 不是永久存储。用户清理网站数据、浏览器存储空间不足或存储配额变化,都可能导致缓存被删除。应用必须能够处理缓存未命中。
4. 为什么 Service Worker 更新后页面仍然使用旧资源
可能是以下原因:
- 新 Worker 处于 waiting 状态;
- HTML 使用了过于激进的 Cache First;
- 旧页面仍被旧 Worker 控制;
- 静态资源文件名没有版本指纹;
- 开发者工具缓存或浏览器 HTTP 缓存仍然生效。
5. 如何调试 Service Worker
Chrome DevTools 的 Application 面板可以查看:
- Service Workers 的注册、状态和作用域;
- Cache Storage 的内容;
- IndexedDB 数据;
- Update on reload;
- Skip waiting;
- Unregister;
- Clear storage。
这些选项主要用于开发调试,不能代替正式环境的版本更新方案。
6. 如何避免资源泄漏
- 页面销毁时调用
worker.terminate(); - 不要无限创建 Worker;
- Service Worker 缓存使用版本号;
- 在
activate中删除旧缓存; - 限制图片和接口缓存的数量与有效期;
- 不要将访问令牌、密码等敏感数据放进缓存。
九、总结
- Web Worker 主要解决页面主线程计算阻塞问题;
- Service Worker 主要解决网络代理、缓存、离线和后台事件问题;
- 两者都不能直接访问页面 DOM;
- Worker 通过
postMessage()通信,复杂数据可以使用 Transferable 减少复制; - Service Worker 需要 HTTPS 或 localhost,并且受注册作用域和页面控制状态限制;
- Service Worker 不是永久运行进程,不能依赖内存全局变量保存持久数据;
- 缓存策略应根据资源实时性、隐私和离线需求选择;
skipWaiting()和clients.claim()应谨慎组合,避免新旧页面和缓存版本混用;- Push、Background Sync 和 Periodic Background Sync 都需要特性检测和降级方案;
- 新的 Vue、Vite、Webpack 和 Nuxt 项目通常不需要
worker-loader; - 发布前应测试首次访问、刷新、更新、离线、缓存清理、权限拒绝和多标签页场景。