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
默认情况下,消息数据会按 结构化克隆 复制一份:
- 能传普通对象、数组、
Map、Set、ArrayBuffer等 - 但不能传函数、DOM 节点等
如果数据很大,可用 Transferable:
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);
这样做的含义:
- 不复制
- 直接把
buffer的所有权转给 Worker - 主线程侧这个
buffer会失效
面试口径:
- 结构化克隆是“拷贝”
- Transferable 是“转移所有权”
五、Worker 里能做什么
常见可用能力:
fetchWebSocket- 定时器
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。