Service Worker 与 Web Worker 有什么区别?
Service Worker 与 Web Worker 的核心区别
两者都是浏览器提供的后台线程机制,但设计目标完全不同:Web Worker 解决的是主线程阻塞问题,Service Worker 解决的是网络请求控制问题。
一图看懂关键差异
| 维度 | Service Worker | Web Worker |
|---|---|---|
| 定位 | 网络代理,拦截和控制请求 | 后台线程,执行耗时计算 |
| 生命周期 | 独立于页面,可被浏览器自动重启 | 随页面存活,页面关闭即销毁 |
| DOM 访问 | 不可访问 | 不可访问 |
| 网络拦截 | 可以拦截所有作用域内的请求 | 无法拦截 |
| 通信方式 | postMessage + clients API | postMessage |
| 安全要求 | 仅限 HTTPS(localhost 例外) | HTTP/HTTPS 均可 |
| 作用域 | 由注册路径决定,默认限 scope 内 | 仅与创建它的页面通信 |
| 持久化 | 浏览器关闭后仍可被唤醒 | 不可 |
Service Worker 的核心能力
网络代理与缓存策略
Service Worker 最本质的能力是充当网络代理,通过监听 fetch 事件拦截请求,配合 Cache API 实现多种缓存策略:
javascript// Cache First:优先从缓存取,缓存没有再走网络 self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(cached => { return cached || fetch(event.request); }) ); }); // Network First:优先走网络,失败再回退缓存 self.addEventListener('fetch', event => { event.respondWith( fetch(event.request).catch(() => caches.match(event.request)) ); });
常见缓存策略的选择依据:静态资源用 Cache First,频繁更新的接口用 Network First,非关键请求用 Stale While Revalidate。
独立生命周期
Service Worker 有完整的 install → waiting → activate 流程。安装后即使所有页面关闭,浏览器仍可在需要时重新唤醒它,这是它能处理推送通知和后台同步的前提。
javascript// 推送通知 self.addEventListener('push', event => { event.waitUntil( self.registration.showNotification('新消息', { body: event.data.text() }) ); }); // 后台同步 self.addEventListener('sync', event => { if (event.tag === 'sync-data') { event.waitUntil(syncData()); } });
作用域控制
Service Worker 的作用域由注册时的路径决定。在 /sw.js 注册会控制整个站点,在 /app/sw.js 注册只控制 /app/ 路径下的请求。可以通过 scope 参数显式指定,但无法超出脚本所在目录的范围。
Web Worker 的核心能力
耗时计算的离线处理
Web Worker 的设计目的很简单:把耗时计算移出主线程,防止 UI 卡顿。
javascript// main.js const worker = new Worker('worker.js'); worker.postMessage({ data: largeArray }); worker.onmessage = event => { console.log('计算结果:', event.data); }; // worker.js self.onmessage = event => { const result = heavyComputation(event.data); self.postMessage(result); };
生命周期与页面绑定
Web Worker 的生命周期严格绑定创建它的页面。页面关闭,Worker 销毁,不存在"浏览器自动重启"的机制。这决定了它适合一次性或页面级的计算任务,不适合后台持续运行。
三种 Worker 类型
javascript// Dedicated Worker — 一对一,最常用 const worker = new Worker('worker.js'); // Shared Worker — 多页面共享同一个 Worker 实例 const sharedWorker = new SharedWorker('shared-worker.js'); sharedWorker.port.start(); sharedWorker.port.postMessage('hello'); // Service Worker — 网络代理型 Worker navigator.serviceWorker.register('/sw.js');
Shared Worker 适合多标签页需要共享状态的场景(如 WebSocket 连接复用),但它无法拦截网络请求,这与 Service Worker 有本质区别。
面试常见追问
追问 1:Service Worker 为什么只能在 HTTPS 下使用? 因为 Service Worker 能拦截和篡改网络请求,如果在 HTTP 下运行,中间人攻击者可以注入恶意 Worker 篡改所有响应。localhost 是唯一例外,方便本地开发。
追问 2:Service Worker 更新后如何生效? 浏览器会字节级对比新旧 SW 文件,发现不同会启动新的 Worker 进入 install 状态。但旧 Worker 不会立即被替换,需等所有受控页面关闭后新 Worker 才进入 activate 状态。可通过 self.skipWaiting() 和 clients.claim() 加速生效,但生产环境需谨慎使用,避免新旧缓存不一致。
追问 3:Web Worker 能操作 DOM 吗? 不能。Worker 运行在独立线程,没有 DOM API。需要操作 DOM 时,把计算结果通过 postMessage 发回主线程,由主线程执行 DOM 操作。
追问 4:Service Worker 和 Web Worker 能同时使用吗? 可以且推荐。Service Worker 负责离线缓存和网络策略,Web Worker 负责复杂计算。例如一个图片编辑 PWA:Service Worker 缓存图片资源,Web Worker 执行图片滤镜计算,主线程只负责渲染和交互。