Web Worker 全面指南

1. 什么是 Web Worker?

Web Worker 使得在 Web 应用程序中,主执行线程之外的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)运行不会被阻塞/放慢。

核心原理: JavaScript 是单线程的,但浏览器内核是多线程的。Web Worker 利用这一点,允许我们把原本需要 JS 引擎执行的计算任务,分给浏览器的一个子线程去执行。

2. 为什么需要它?(解决痛点)

在没有 Worker 之前,如果我们在 JS 中执行大量的 CPU 计算(例如处理百万级数据排序、图像像素处理、加密解密),主线程会被长时间占用。

结果:页面无法响应用户的点击、无法滚动、动画卡顿,甚至浏览器弹出“页面无响应”警告。

使用 Worker 后:计算在后台默默进行,主线程依然流畅响应用户交互。

3. Worker 的类型

(1) Dedicated Worker (专用 Worker)

  • 最常见。
  • 只能被 生成它的脚本 所使用。
  • 通常用于单个页面内部的复杂计算。

(2) Shared Worker (共享 Worker)

  • 可以被 多个脚本 共享(例如同源下的多个 Tab 页、iframe)。
  • 通过 port 对象进行通信。
  • 应用场景:跨 Tab 页数据同步、维持唯一的 WebSocket 连接以节省资源。

(3) Service Worker (服务工作线程)

  • 主要用于 PWA (Progressive Web Apps)
  • 充当网络代理,拦截网络请求,实现离线缓存。
  • 生命周期独立于网页,甚至网页关闭后仍在后台运行(用于推送通知)。

4. 关键限制与能力

Web Worker 运行在一个完全独立的全局上下文 (WorkerGlobalScope) 中,它不是 Window

🚫 不能做的 (限制)

  • 无法访问 DOM (document, window, parent)。
  • 无法访问 localStorage (可以使用 IndexedDB)。
  • 无法使用 alert()confirm() (可以使用 consoleXMLHttpRequest/fetch)。

✅ 能做的 (能力)

  • navigator 对象 (部分属性,如 userAgent)。
  • location 对象 (只读)。
  • XMLHttpRequest / fetch (发起网络请求)。
  • setTimeout / setInterval
  • importScripts() (导入外部脚本)。
  • 创建子 Worker

5. 实战代码示例

(1) 主线程 (main.js)

// 1. 创建 Worker
const worker = new Worker('worker.js');

// 2. 发送数据
worker.postMessage({ type: 'start', payload: [1, 2, 3] });

// 3. 接收数据
worker.onmessage = function(e) {
    console.log('Main: Received result', e.data);
    // 任务完成,记得关闭
    worker.terminate();
};

// 4. 错误处理
worker.onerror = function(err) {
    console.error('Error in worker:', err.message);
};

(2) Worker 线程 (worker.js)

// 1. 监听消息
self.onmessage = function(e) {
    const { type, payload } = e.data;
    if (type === 'start') {
        const result = expensiveComputation(payload);
        // 2. 发回结果
        self.postMessage(result);
        // 也可自我关闭
        // self.close();
    }
};

function expensiveComputation(data) {
    // 模拟耗时计算
    return data.map(x => x * 2);
}

6. 面试高频问题

Q: 主线程和 Worker 之间传递数据是引用的吗?

不是。默认情况下,数据是通过 结构化克隆算法 (Structured Clone Algorithm) 进行拷贝的。这意味着传递大对象(如 100MB 的 ArrayBuffer)会有显著的拷贝开销。

Q: 如何优化大数据传输?

使用 Transferable Objects (可转移对象)。 通过 postMessage(data, [transferable]),将数据的所有权直接转移给 Worker。转移后,主线程将无法再访问该数据(长度变为 0),实现零拷贝。 适用于 ArrayBuffer, MessagePort, ImageBitmap 等。

const hugeBuffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
worker.postMessage(hugeBuffer, [hugeBuffer]); // Zero-copy transfer

Q: 如何解决 Worker 加载跨域脚本问题?

标准限制:Worker 构造函数只能接受同源脚本 URL。 new Worker('http://other.com/worker.js') 会报错。

解决方案

  1. CORS:服务端配置 Access-Control-Allow-Origin 响应头,允许跨域访问脚本资源。
  2. Blob URL:先通过 fetch 获取跨域脚本内容 → 转换为 Blob → 创建本地 Blob URL。
fetch('http://other.com/worker.js')
  .then(res => res.blob())
  .then(blob => new Worker(URL.createObjectURL(blob)));

注意:使用 Blob URL 方式时,仍需要跨域资源本身支持 CORS,否则 fetch 会失败。

Q: Worker 会造成内存泄漏吗?

。Worker 线程创建后,除非手动调用 worker.terminate() 或在 Worker 内部调用 self.close(),否则会一直存在。如果在单页应用(SPA)中频繁创建 Worker 而不销毁,会导致内存占用不断增加。

最佳实践

  • 在任务完成后及时调用 worker.terminate() 销毁 Worker。
  • 在 React 组件卸载时(useEffect cleanup)销毁 Worker。
  • 在 Vue 组件销毁前(beforeUnmount/onBeforeUnmount)销毁 Worker。
// React 示例
useEffect(() => {
    const worker = new Worker('worker.js');
    
    worker.onmessage = (e) => {
        console.log(e.data);
    };
    
    // 清理函数
    return () => {
        worker.terminate();
    };
}, []);

7. 总结

Web Worker 是前端性能优化的重要手段,特别是在处理复杂计算时。它的核心设计理念是 UI 渲染与业务逻辑分离,通过将耗时操作移至后台线程,保持主线程的流畅响应。

关键要点

  • ✅ 适用于 CPU 密集型任务(数据处理、加密、图像处理)
  • ⚠️ 注意通信开销(数据序列化/反序列化)
  • 🔄 大数据传输优先使用 Transferable Objects
  • 🧹 及时销毁 Worker,避免内存泄漏
  • 🚫 无法访问 DOM,不适合 UI 相关操作