前端 Web Workers 和 Service Workers 全解析与应用

一、为什么前端需要 Workers

浏览器中的 JavaScript 默认运行在主线程。主线程不仅负责执行 JS,还负责页面渲染、样式计算、布局、绘制、事件响应等工作。如果某段 JS 长时间占用主线程,页面就会出现卡顿、点击无响应、动画掉帧等问题。

Workers 的出现,是为了把部分任务从主线程中拆出去,让浏览器可以更流畅地处理用户交互和页面渲染。

常见 Worker 类型包括:

  • Web Worker:把计算任务放到后台线程执行。
  • Dedicated Worker:专属于一个页面或脚本的 Worker,最常见。
  • Shared Worker:可被同源多个页面共享的 Worker。
  • Service Worker:运行在浏览器和网络之间的代理层,常用于缓存、离线、PWA、推送。
  • Worklet:更轻量的专用工作线程,例如 AudioWorklet、PaintWorklet。
flowchart TD A[浏览器主线程] --> B[页面渲染] A --> C[用户交互] A --> D[普通 JavaScript] D --> E[耗时任务导致卡顿] E --> F[使用 Workers 拆分任务] F --> G[Web Worker 处理计算] F --> H[Service Worker 处理网络和缓存]

二、Web Worker 和 Service Worker 的核心区别

Web Worker 和 Service Worker 都能在主线程之外运行 JavaScript,但它们的定位完全不同。

对比项 Web Worker Service Worker
核心用途 后台计算、多线程任务 网络代理、缓存、离线、PWA
生命周期 通常由页面创建和销毁 独立于页面,有安装、激活、终止等生命周期
是否能访问 DOM 不能 不能
是否能拦截请求 不能 可以拦截受控页面的网络请求
通信方式 postMessage postMessageclients、事件机制
使用限制 同源脚本或可访问脚本 通常要求 HTTPS 或 localhost
典型场景 大数据计算、图片处理、加密压缩 离线访问、静态资源缓存、消息推送

一句话概括:Web Worker 解决"计算别卡主线程",Service Worker 解决"网络和缓存可控"。

flowchart TD A[前端性能和离线需求] --> B[计算密集任务] A --> C[网络缓存和离线能力] B --> D[选择 Web Worker] C --> E[选择 Service Worker] D --> F[不阻塞主线程] E --> G[拦截请求和管理缓存]

三、浏览器线程模型基础

主线程是前端最关键的执行线程,页面大部分工作都发生在主线程上。

主线程常见任务:

  • 执行 JavaScript。
  • 解析 HTML。
  • 计算 CSS 样式。
  • 执行布局 Layout。
  • 执行绘制 Paint。
  • 处理输入事件。
  • 执行动画回调。

如果 JavaScript 长时间执行,浏览器无法及时渲染和响应用户输入。

flowchart TD A[用户操作页面] --> B[主线程接收事件] B --> C[执行 JavaScript] C --> D[计算样式和布局] D --> E[绘制页面] E --> F[用户看到更新] C --> G[长任务超过 50ms] G --> H[页面卡顿和输入延迟]

Web Worker 的价值就是把耗时 JS 任务从主线程移到后台线程。主线程只负责发送任务、接收结果和更新 UI。

四、Web Worker 基础用法

1. 创建 Worker

主线程代码:

js 复制代码
const worker = new Worker('/worker.js');

worker.postMessage({ type: 'sum', payload: [1, 2, 3] });

worker.onmessage = event => {
  console.log('计算结果:', event.data);
};

worker.onerror = error => {
  console.error('Worker 出错:', error);
};

Worker 文件 worker.js

js 复制代码
self.onmessage = event => {
  const { type, payload } = event.data;

  if (type === 'sum') {
    const result = payload.reduce((total, item) => total + item, 0);
    self.postMessage(result);
  }
};

2. Worker 通信模型

主线程和 Worker 之间不能直接共享普通对象,它们主要通过消息通信。

flowchart TD A[主线程] --> B[postMessage 发送任务] B --> C[Worker 线程接收消息] C --> D[执行耗时计算] D --> E[postMessage 返回结果] E --> F[主线程接收结果] F --> G[更新页面 UI]

3. 终止 Worker

主线程可以主动终止 Worker:

js 复制代码
worker.terminate();

Worker 内部也可以自己关闭:

js 复制代码
self.close();

长期不用的 Worker 应及时释放,避免占用内存和线程资源。

五、Web Worker 的运行环境

Worker 不是浏览器窗口环境,它没有完整的 DOM API。

Worker 中不能使用:

  • document
  • window 作为页面窗口对象
  • DOM 查询和操作
  • 直接修改页面 UI
  • 部分浏览器 UI API

Worker 中可以使用:

  • self
  • setTimeoutsetInterval
  • fetch
  • XMLHttpRequest
  • Promise
  • WebSocket
  • IndexedDB
  • crypto
  • importScripts,经典 Worker 中可用
js 复制代码
self.addEventListener('message', event => {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => self.postMessage(data));
});

注意:Worker 能发起请求,但不能像 Service Worker 一样拦截页面请求。

六、Worker 的数据传递机制

1. 结构化克隆

postMessage 默认使用结构化克隆算法复制数据。它可以传递对象、数组、Map、Set、Blob、File、ArrayBuffer 等。

js 复制代码
worker.postMessage({
  list: [1, 2, 3],
  meta: new Map([['source', 'main']])
});

结构化克隆不是 JSON 序列化,它支持更多数据类型,但函数、DOM 节点等不能被克隆。

2. Transferable 对象

大数据传递时,复制成本可能很高。可以使用 Transferable 把数据所有权转移给 Worker。

js 复制代码
const buffer = new ArrayBuffer(1024 * 1024);
worker.postMessage(buffer, [buffer]);

转移后,主线程中的 buffer 会变为不可用。这样可以避免大块内存复制。

3. SharedArrayBuffer

SharedArrayBuffer 可以让多个线程共享同一块内存,配合 Atomics 做同步控制。

js 复制代码
const shared = new SharedArrayBuffer(4);
const view = new Int32Array(shared);
worker.postMessage(shared);
Atomics.store(view, 0, 1);

它适合高性能场景,但对安全环境要求更高,通常需要跨源隔离相关响应头。

flowchart TD A[主线程发送数据] --> B[普通对象] A --> C[ArrayBuffer] A --> D[SharedArrayBuffer] B --> E[结构化克隆复制] C --> F[Transferable 转移所有权] D --> G[共享内存配合 Atomics] E --> H[适合普通数据] F --> I[适合大块二进制数据] G --> J[适合高性能并发]

七、Web Worker 的典型应用

1. 大数据计算

例如前端需要对几十万条数据进行筛选、聚合、排序、统计,如果直接在主线程执行,很容易卡住页面。

js 复制代码
// main.js
const worker = new Worker('/data-worker.js');
worker.postMessage({ type: 'aggregate', list: bigList });
worker.onmessage = event => {
  renderChart(event.data);
};
js 复制代码
// data-worker.js
self.onmessage = event => {
  const { list } = event.data;
  const result = list.reduce((map, item) => {
    map[item.type] = (map[item.type] || 0) + item.value;
    return map;
  }, {});

  self.postMessage(result);
};

2. 图片处理

图片压缩、滤镜、裁剪、像素分析都可能消耗大量 CPU。可以结合 Worker 和 OffscreenCanvas。

js 复制代码
const canvas = document.querySelector('canvas');
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

Worker 中处理:

js 复制代码
self.onmessage = event => {
  const { canvas } = event.data;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'red';
  ctx.fillRect(0, 0, 100, 100);
};

3. 文件解析

Excel、CSV、日志文件、大 JSON 文件都可以在 Worker 中解析,避免主线程卡顿。

4. 加密、压缩和解压

哈希计算、签名、压缩、解压等 CPU 密集型任务适合放入 Worker。

5. 前端搜索和索引

本地全文检索、模糊搜索、索引构建可以放到 Worker 中,主线程只负责展示结果。

6. WebAssembly 配合 Worker

高性能计算模块可以用 WebAssembly 运行在 Worker 中,例如音视频处理、图像算法、CAD、游戏逻辑等。

flowchart TD A[适合 Web Worker 的任务] --> B[CPU 密集] A --> C[数据量大] A --> D[可异步返回] A --> E[不直接操作 DOM] B --> F[计算和加密] C --> G[文件解析和数据聚合] D --> H[搜索和索引] E --> I[图片处理和 WASM]

八、Web Worker 的工程化使用

1. Vite 中创建 Worker

js 复制代码
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'module'
});

2. Webpack 5 中创建 Worker

js 复制代码
const worker = new Worker(new URL('./worker.js', import.meta.url));

3. TypeScript 中使用 Worker

可以给消息定义明确类型:

ts 复制代码
type WorkerRequest =
  | { type: 'sum'; payload: number[] }
  | { type: 'sort'; payload: number[] };

type WorkerResponse =
  | { type: 'sumResult'; payload: number }
  | { type: 'sortResult'; payload: number[] };

这样主线程和 Worker 之间的协议更稳定。

4. 封装 Promise 风格调用

js 复制代码
function callWorker(worker, payload) {
  return new Promise((resolve, reject) => {
    const id = crypto.randomUUID();

    function handleMessage(event) {
      if (event.data.id === id) {
        worker.removeEventListener('message', handleMessage);
        resolve(event.data.result);
      }
    }

    worker.addEventListener('message', handleMessage);
    worker.addEventListener('error', reject, { once: true });
    worker.postMessage({ id, payload });
  });
}

复杂项目中建议定义统一消息协议、错误协议和超时机制。

九、Web Worker 的限制和注意事项

1. 不要把所有任务都丢给 Worker

Worker 创建和通信都有成本。如果任务很轻,放进 Worker 反而可能更慢。

2. 注意大对象复制成本

大对象频繁 postMessage 可能带来明显性能开销。二进制大数据优先考虑 Transferable。

3. Worker 不能直接操作 DOM

Worker 计算完结果后,必须通知主线程,由主线程更新页面。

4. 注意错误处理

Worker 报错不会像普通代码一样直接显示在主线程调用栈中,应监听 errormessageerror

js 复制代码
worker.addEventListener('error', error => {
  console.error(error.message, error.filename, error.lineno);
});

worker.addEventListener('messageerror', error => {
  console.error('消息反序列化失败', error);
});

5. 注意资源释放

页面卸载、组件销毁、任务完成后,应根据场景调用 terminate()

十、Shared Worker 简介

Shared Worker 可以被同源下多个页面、iframe 或脚本共享。它适合跨标签页共享连接、共享状态、协调任务。

主页面:

js 复制代码
const sharedWorker = new SharedWorker('/shared-worker.js');
sharedWorker.port.start();

sharedWorker.port.postMessage({ type: 'connect' });
sharedWorker.port.onmessage = event => {
  console.log(event.data);
};

Shared Worker:

js 复制代码
const ports = [];

self.onconnect = event => {
  const port = event.ports[0];
  ports.push(port);

  port.onmessage = messageEvent => {
    ports.forEach(item => {
      item.postMessage(messageEvent.data);
    });
  };
};

典型应用:

  • 多标签页共享 WebSocket。
  • 多页面共享缓存状态。
  • 多窗口消息广播。
  • 减少重复后台任务。

十一、Service Worker 是什么

Service Worker 是一种特殊 Worker。它运行在浏览器后台,位于页面和网络之间,可以拦截请求、读取缓存、返回自定义响应。

它是 PWA 的核心技术之一。

flowchart TD A[页面发起请求] --> B[Service Worker 拦截 fetch] B --> C[检查缓存] C --> D[缓存命中] C --> E[缓存未命中] D --> F[返回缓存响应] E --> G[请求网络] G --> H[更新缓存] H --> I[返回网络响应]

Service Worker 的关键特征:

  • 不能直接访问 DOM。
  • 可以拦截受控页面的网络请求。
  • 可以使用 Cache Storage。
  • 可以在页面关闭后被浏览器唤醒处理事件。
  • 生命周期由浏览器管理,不由页面完全控制。
  • 通常必须运行在 HTTPS 或 localhost 环境下。

十二、注册 Service Worker

页面中注册:

js 复制代码
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker 注册成功', registration.scope);
    } catch (error) {
      console.error('Service Worker 注册失败', error);
    }
  });
}

scope 决定 Service Worker 能控制哪些路径下的页面。默认情况下,/sw.js 可以控制整个站点,/assets/sw.js 通常只能控制 /assets/ 路径下的资源。

十三、Service Worker 生命周期

Service Worker 的生命周期比普通脚本复杂,主要包括注册、安装、激活、控制页面、空闲终止、事件唤醒。

flowchart TD A[页面注册 Service Worker] --> B[浏览器下载 sw.js] B --> C[install 安装事件] C --> D[等待旧版本释放控制] D --> E[activate 激活事件] E --> F[控制页面请求] F --> G[空闲时被终止] G --> H[有事件时再次唤醒]

1. install 阶段

通常用于预缓存关键资源。

js 复制代码
const CACHE_NAME = 'app-cache-v1';
const PRE_CACHE = ['/', '/index.html', '/styles.css', '/main.js'];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(PRE_CACHE))
  );
});

2. activate 阶段

通常用于清理旧缓存。

js 复制代码
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))
      );
    })
  );
});

3. fetch 阶段

用于拦截请求并返回缓存或网络响应。

js 复制代码
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

十四、Service Worker 更新机制

Service Worker 更新经常让开发者困惑。浏览器会定期检查 sw.js 是否变化。如果发现文件内容变化,会安装新版本。

但是新版本不会总是立即接管页面。默认行为是:

  1. 新 Service Worker 下载并安装。
  2. 如果旧页面仍被旧 Service Worker 控制,新版本进入 waiting 状态。
  3. 等所有旧页面关闭后,新版本才激活。
  4. 新版本激活后控制后续页面。
flowchart TD A[浏览器发现 sw.js 更新] --> B[安装新版本] B --> C[旧页面仍在运行] C --> D[新版本进入 waiting] D --> E[旧页面关闭] E --> F[新版本 activate] F --> G[控制新页面]

如果希望新版本尽快生效,可以使用:

js 复制代码
self.addEventListener('install', event => {
  self.skipWaiting();
});

self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim());
});

但要谨慎使用,因为新旧资源混用可能导致页面异常。生产环境通常需要结合版本提示,让用户刷新。

十五、Cache Storage 基础

Service Worker 常用 Cache Storage 管理请求和响应。

js 复制代码
const cache = await caches.open('app-cache-v1');
await cache.put('/api/user', response.clone());
const cached = await cache.match('/api/user');

注意事项:

  • Response 流只能读取一次,缓存前通常需要 response.clone()
  • Cache Storage 不会自动过期,需要自己管理版本或数量。
  • 不要缓存包含敏感信息的私有响应,除非确认安全策略。
  • POST 请求默认不适合作为普通静态缓存键。

十六、常见缓存策略

1. Cache First

先读缓存,缓存没有再请求网络。适合版本化静态资源。

js 复制代码
async function cacheFirst(request) {
  const cached = await caches.match(request);
  return cached || fetch(request);
}

流程:

flowchart TD A[请求资源] --> B[查找缓存] B --> C[命中缓存] B --> D[未命中缓存] C --> E[返回缓存] D --> F[请求网络] F --> G[返回网络结果]

2. Network First

先请求网络,失败时回退缓存。适合 HTML、接口数据等需要新鲜度的资源。

js 复制代码
async function networkFirst(request) {
  try {
    const response = await fetch(request);
    const cache = await caches.open('runtime-cache');
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    return caches.match(request);
  }
}

3. Stale While Revalidate

先返回缓存,同时后台请求网络更新缓存。适合兼顾速度和新鲜度的资源。

js 复制代码
async function staleWhileRevalidate(request) {
  const cache = await caches.open('runtime-cache');
  const cached = await cache.match(request);

  const networkPromise = fetch(request).then(response => {
    cache.put(request, response.clone());
    return response;
  });

  return cached || networkPromise;
}
flowchart TD A[请求资源] --> B[读取缓存] B --> C[有缓存则立即返回] B --> D[同时请求网络] D --> E[网络响应更新缓存] B --> F[无缓存则等待网络] F --> E[网络响应更新缓存]

4. Network Only

完全走网络,不缓存。适合支付、下单、敏感接口等。

5. Cache Only

只读缓存,不请求网络。适合离线包内固定资源。

十七、Service Worker 的典型应用

1. 离线访问

预缓存 HTML、CSS、JS、图片和离线兜底页,让用户在弱网或断网时仍能打开基础页面。

js 复制代码
self.addEventListener('fetch', event => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline.html'))
    );
  }
});

2. PWA 应用

Service Worker 配合 Web App Manifest,可以实现类似原生应用的体验:

  • 添加到桌面。
  • 离线可用。
  • 启动页和图标。
  • 后台同步。
  • 消息推送。

3. 静态资源加速

对带 hash 的静态资源使用 Cache First,可以减少重复下载。

4. 接口缓存

对部分低频变化、可容忍短暂过期的数据使用 Network First 或 Stale While Revalidate。

5. 请求降级和兜底

网络失败时返回缓存、默认数据、兜底图或离线页面,提高弱网体验。

6. Push 推送

Service Worker 可以接收 Push 事件并展示通知。

js 复制代码
self.addEventListener('push', event => {
  const data = event.data ? event.data.json() : { title: '新消息' };

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body || '你有一条新通知'
    })
  );
});

7. Background Sync 后台同步

用户离线提交数据时,可以先存入 IndexedDB,等网络恢复后由 Service Worker 同步。

flowchart TD A[用户离线提交表单] --> B[页面写入 IndexedDB] B --> C[注册后台同步任务] C --> D[网络恢复] D --> E[Service Worker 被唤醒] E --> F[读取 IndexedDB] F --> G[提交到服务端]

十八、Service Worker 安全注意事项

1. 必须使用 HTTPS

Service Worker 能拦截请求,权限很高,因此浏览器要求它运行在安全上下文中。开发环境 localhost 通常被允许。

2. 谨慎设置作用域

scope 越大,Service Worker 能控制的页面越多。不要让不相关路径被错误控制。

3. 避免缓存敏感数据

用户隐私、鉴权响应、个人接口数据如果进入 Cache Storage,可能带来安全和合规风险。

4. 防止缓存投毒

不要无条件缓存所有网络响应,应检查状态码、请求方法、资源类型、来源域名。

js 复制代码
if (request.method === 'GET' && response.ok && request.url.startsWith(location.origin)) {
  cache.put(request, response.clone());
}

5. 注意登出清理缓存

用户退出登录时,应按业务要求清理用户相关缓存,避免下一个用户看到前一个用户的数据。

6. 避免无限缓存增长

Cache Storage 不会自动帮业务做精细淘汰,长期运行可能膨胀,需要版本管理、最大数量限制或过期策略。

十九、Web Worker 与 Service Worker 通信

页面可以和 Service Worker 通信:

js 复制代码
navigator.serviceWorker.controller?.postMessage({ type: 'PING' });

navigator.serviceWorker.addEventListener('message', event => {
  console.log('来自 Service Worker:', event.data);
});

Service Worker 回复页面:

js 复制代码
self.addEventListener('message', event => {
  event.source.postMessage({ type: 'PONG' });
});

也可以向所有受控页面广播:

js 复制代码
self.clients.matchAll().then(clients => {
  clients.forEach(client => {
    client.postMessage({ type: 'CACHE_UPDATED' });
  });
});
flowchart TD A[页面] --> B[发送消息给 Service Worker] B --> C[Service Worker 处理消息] C --> D[查询缓存或执行任务] D --> E[回复当前页面] D --> F[广播给所有受控页面]

Web Worker 和 Service Worker 一般不直接互相通信,常见方式是通过页面中转,或者通过 IndexedDB、BroadcastChannel 等共享机制协作。

二十、BroadcastChannel 跨上下文通信

BroadcastChannel 可以让同源下的多个页面、iframe、Worker、Service Worker 进行广播通信。

页面中:

js 复制代码
const channel = new BroadcastChannel('app-channel');
channel.postMessage({ type: 'USER_LOGOUT' });
channel.onmessage = event => {
  console.log(event.data);
};

Worker 或 Service Worker 中也可以创建同名频道监听消息。

适合场景:

  • 多标签页同步登录状态。
  • 通知缓存更新。
  • 多页面共享任务进度。
  • 页面和 Worker 之间解耦通信。

二十一、两者组合的实际架构

一个复杂前端应用可能同时使用 Web Worker 和 Service Worker:

  • Service Worker 负责静态资源缓存、离线兜底、接口缓存。
  • Web Worker 负责大数据计算、文件解析、图像处理。
  • 页面主线程负责 UI 渲染和用户交互。
  • IndexedDB 负责跨线程持久化数据。
flowchart TD A[用户页面] --> B[主线程 UI] B --> C[Web Worker] C --> D[执行计算任务] B --> E[Service Worker] E --> F[拦截请求] E --> G[Cache Storage] C --> H[IndexedDB] E --> H[IndexedDB] D --> B[返回计算结果] G --> B[返回缓存资源]

这种架构适合:

  • 离线优先应用。
  • 大型数据看板。
  • 在线文档编辑器。
  • 图片、音视频处理工具。
  • 本地优先的前端数据库应用。
  • PWA 移动 Web 应用。

二十二、性能优化建议

1. 识别长任务

使用 Performance 面板观察主线程长任务。如果某段 JS 超过 50ms,就可能影响用户交互。

2. 只把合适任务放入 Worker

适合 Worker 的任务通常具备:计算量大、可异步、输入输出清晰、不依赖 DOM。

3. 控制通信频率

不要高频发送大量小消息,可以批处理、节流或使用共享内存。

4. 使用 Transferable 优化大数据传输

图片、音视频、二进制文件优先使用 ArrayBuffer 转移所有权。

5. 合理设计缓存策略

Service Worker 缓存不是越多越好,应按资源类型选择策略。

6. 监控缓存命中率和错误率

缓存策略上线后,需要观察资源加载失败、白屏、版本不一致等问题。

二十三、常见问题排查

1. Web Worker 加载失败

检查:

  • Worker 文件路径是否正确。
  • 是否满足同源要求。
  • 构建工具是否正确处理 Worker 文件。
  • MIME 类型是否正确。
  • 是否使用了当前浏览器不支持的模块语法。

2. Worker 中访问 DOM 报错

这是正常限制。应在 Worker 中计算,在主线程中更新 DOM。

3. Service Worker 注册成功但不拦截请求

检查:

  • 页面是否在 Service Worker 的 scope 下。
  • 当前页面是否已经被 Service Worker 控制。
  • 是否需要刷新一次页面。
  • fetch 事件是否正确 respondWith
  • 是否在 HTTPS 或 localhost 下运行。

4. Service Worker 更新不生效

检查:

  • sw.js 内容是否真的变化。
  • 新 Worker 是否处于 waiting 状态。
  • 是否有旧页面未关闭。
  • 是否需要提示用户刷新。
  • 是否错误缓存了 sw.js 本身。

5. 页面出现新旧资源混用

常见原因是 HTML 使用新版本,但 JS/CSS 被旧缓存命中,或反过来。建议静态资源文件名带 hash,HTML 使用 Network First 或不强缓存。

flowchart TD A[Workers 问题] --> B[判断是 Web Worker 还是 Service Worker] B --> C[Web Worker 问题] B --> D[Service Worker 问题] C --> E[检查路径和通信] C --> F[检查数据复制和错误事件] D --> G[检查注册和 scope] D --> H[检查生命周期和缓存策略] H --> I[检查版本更新和缓存清理]

二十四、最佳实践清单

Web Worker 最佳实践

  • 只处理 CPU 密集或大数据任务。
  • 建立清晰的消息协议。
  • 给请求加唯一 ID,方便异步响应匹配。
  • 大数据使用 Transferable。
  • 监听 errormessageerror
  • 页面销毁时及时 terminate()
  • 不要在 Worker 中依赖 DOM 和浏览器 UI。

Service Worker 最佳实践

  • 只在 HTTPS 或 localhost 下启用。
  • 明确 scope 范围。
  • 不缓存敏感接口和不安全响应。
  • HTML、静态资源、接口使用不同缓存策略。
  • 版本升级时清理旧缓存。
  • 对更新流程做用户提示。
  • 登出时清理用户相关缓存。
  • 避免缓存 sw.js 本身导致更新异常。

二十五、面试常见问题

1. Web Worker 为什么不能操作 DOM

因为 DOM 主要由主线程管理。如果多个线程同时直接修改 DOM,会带来复杂的并发一致性问题。Worker 通过消息把计算结果交给主线程,由主线程统一更新 UI。

2. Web Worker 能不能提升所有 JS 性能

不能。Worker 有创建成本和通信成本,只适合计算量较大、可异步拆分的任务。轻量逻辑放进 Worker 可能得不偿失。

3. Service Worker 和浏览器 HTTP 缓存有什么区别

HTTP 缓存由浏览器根据响应头自动管理,Service Worker 缓存由业务代码控制。Service Worker 可以决定缓存策略、离线兜底和自定义响应,但也需要开发者负责更新和清理。

4. Service Worker 为什么要求 HTTPS

因为它能拦截和改写请求响应。如果在不安全网络中被篡改,会造成严重安全风险。HTTPS 可以保证 Service Worker 脚本来源可信。

5. Service Worker 更新为什么不是立即生效

为了避免正在运行的页面被新脚本突然接管导致状态不一致,浏览器默认让新版本等待旧页面关闭后再激活。

6. PWA 一定要 Service Worker 吗

严格来说,完整 PWA 能力通常离不开 Service Worker,尤其是离线缓存、请求代理、后台同步、推送通知等能力。

二十六、总结

Web Worker 和 Service Worker 都是现代前端非常重要的后台能力,但它们解决的问题不同。

Web Worker 的重点是性能:

  • 把耗时计算移出主线程。
  • 避免页面卡顿。
  • 适合大数据、文件、图片、加密、WASM 等任务。

Service Worker 的重点是网络和离线:

  • 拦截请求。
  • 管理缓存。
  • 支持离线访问。
  • 支撑 PWA、推送、后台同步等能力。

选择时可以记住:

flowchart TD A[遇到前端问题] --> B[主线程计算卡顿] A --> C[弱网离线和缓存控制] B --> D[使用 Web Worker] C --> E[使用 Service Worker] D --> F[优化计算和响应速度] E --> G[优化加载和离线体验]

在实际工程中,二者可以组合使用:主线程负责 UI,Web Worker 负责计算,Service Worker 负责网络和缓存,IndexedDB 负责持久化数据。这样可以构建出更流畅、更可靠、更接近原生体验的现代 Web 应用。

相关推荐
Moment8 小时前
从多人编辑到 Agent 写文档,Hocuspocus v4 正在改写协同系统 😍😍😍
前端·后端·面试
星环科技8 小时前
数据标准Agent ,让企业数据说同一种语言
java·开发语言·前端
橘子星8 小时前
深入理解 AJAX 中的 JSON 序列化与 JS 异步处理
前端·javascript·后端
旧曲重听18 小时前
2026前端技术从「夯」到「拉」
前端·程序人生·职场和发展·软件工程
Kapaseker8 小时前
我找到了最适合程序员的 PPT 工具 — Slidev
前端
雾削木9 小时前
B语言经典教程现代化重构
java·前端·stm32·单片机·嵌入式硬件
Cobyte9 小时前
20.Vue Vapor 的应用初始化
前端·javascript·vue.js
乘风gg9 小时前
手把手带你实践历时一年总结的 AI Code Review 最佳工作流!
前端·ai编程·cursor
禅思院9 小时前
POST请求发两次?一次讲透CORS预检机制,面试不再翻车
前端·架构·前端框架