跳到主要内容

Web Worker:为什么它能“开子线程”却不能操作 DOM?主线程和 Worker 怎么通信?

面试速答(30 秒版 TL;DR)

  • Web Worker 让浏览器在 主线程之外 执行 JavaScript,适合放 CPU 密集型任务,避免阻塞 UI。
  • 它不能直接操作 DOM,因为 DOM 不是线程安全结构,浏览器不允许多个线程同时随意改文档树。
  • 主线程和 Worker 通过 postMessage 通信,底层通常走 结构化克隆(structured clone);大数据可配合 Transferable 做零拷贝转移。
  • 高频追问是:Dedicated Worker、Shared Worker、Service Worker 的区别,以及什么时候该用 Worker、什么时候不该用。

心智模型:不是“并行改页面”,而是“把重活搬离主线程”

主线程要负责:

  • JS 执行
  • 事件响应
  • 样式计算
  • 布局和绘制调度

如果一段计算太重:

  • 页面会卡
  • 点击没反应
  • 动画掉帧

Worker 的价值就在于:

  • 把重 CPU 任务搬到别的线程算
  • 主线程继续保持交互流畅

一、最小例子:主线程创建 Worker

const worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});

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

worker.onmessage = (e) => {
console.log("result:", e.data);
};

worker.js

self.onmessage = (e) => {
const result = e.data.payload.reduce((a, b) => a + b, 0);
self.postMessage(result);
};

二、为什么 Worker 不能操作 DOM

标准答法:

  • DOM 是共享的文档树结构
  • 如果多个线程都能同时直接改 DOM,会带来同步、竞争、锁和一致性问题
  • 浏览器为了避免这类线程安全复杂度,限制 Worker 不能直接访问 DOM

所以 Worker 更适合做:

  • 计算
  • 解析
  • 编码/解码
  • 数据处理

而不是直接改页面。


三、通信方式:postMessage

主线程和 Worker 的通信是消息式的:

worker.postMessage({ type: "parse", text });

Worker 里接收:

self.onmessage = (e) => {
console.log(e.data);
};

返回结果:

self.postMessage({ ok: true });

四、结构化克隆 vs Transferable

默认情况下,消息数据会按 结构化克隆 复制一份:

  • 能传普通对象、数组、MapSetArrayBuffer
  • 但不能传函数、DOM 节点等

如果数据很大,可用 Transferable

const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);

这样做的含义:

  • 不复制
  • 直接把 buffer 的所有权转给 Worker
  • 主线程侧这个 buffer 会失效

面试口径:

  • 结构化克隆是“拷贝”
  • Transferable 是“转移所有权”

五、Worker 里能做什么

常见可用能力:

  • fetch
  • WebSocket
  • 定时器
  • crypto
  • 部分流式 API
  • importScripts(经典 worker)或 ES Module(模块 worker)

不能做的事:

  • 直接操作 DOM
  • 直接访问大多数 BOM 交互能力

六、什么时候适合用 Worker

适合

  • 大文件解析
  • 图片处理
  • 音视频转码
  • 大 JSON 解析/格式转换
  • 搜索索引构建
  • 富文本 diff / AST 分析

不适合

  • 很轻的小计算
  • 高频极短任务,线程通信成本反而可能更高
  • 强依赖 DOM 的逻辑

七、Dedicated Worker、Shared Worker、Service Worker 区别

类型作用特点
Worker / Dedicated Worker给当前页面专用最常见,和单页面上下文绑定
SharedWorker多个页面/标签共享支持多连接共享同一 worker
Service Worker网络代理与离线缓存更像浏览器后台代理,不是普通计算线程

面试常见误区:

  • Service Worker 不是普通 Web Worker 的简单升级版
  • 它更偏网络拦截、缓存、离线能力

八、终止与错误处理

worker.terminate();

以及:

worker.onerror = (e) => {
console.error(e.message);
};

如果 Worker 自己要结束,也可:

self.close();

九、实战建议:任务协议化

不要只发裸数据,建议发协议对象:

worker.postMessage({
type: "tokenize",
requestId: "r1",
payload: text,
});

这样好处是:

  • 易扩展
  • 易做并发请求映射
  • 易处理错误和取消

典型题 & 标准答法

Q1:Web Worker 的核心价值是什么?

  • 把重 CPU 任务挪出主线程
  • 避免阻塞页面交互和渲染

Q2:为什么 Web Worker 不能操作 DOM?

因为 DOM 不是线程安全结构,浏览器不希望多个线程直接并发修改文档树。

Q3:结构化克隆和 Transferable 有什么区别?

  • 结构化克隆:复制数据
  • Transferable:转移所有权,避免大对象复制开销

易错点/坑

  • 把很小的任务也丢给 Worker,通信成本可能比计算还高。
  • 误以为 Worker 能直接 document.querySelector
  • 传超大二进制数据却不用 Transferable,导致额外复制开销。
  • 忘记在页面销毁时 terminate() 长生命周期 Worker。

速记要点(可背诵)

  • Web Worker = 浏览器里的后台 JS 线程。
  • 作用是减轻主线程 CPU 压力,不是直接操作页面。
  • 通信靠 postMessage
  • 大数据优化靠 Transferable。