Web Workers 是唯一能真正将 CPU 密集型任务移出主线程的浏览器原生方案;其他异步手段仅让出时间片,不解决线程独占问题,而 postMessage 传大数据变慢甚至卡死,是因为结构化克隆默认深拷贝,10MB ArrayBuffer 会完整序列化反序列化,占用主线程 CPU。

Web Workers 是唯一能真正把 CPU 密集型任务移出主线程的浏览器原生方案。其他所谓“异步”手段(如 setTimeout、Promise、requestIdleCallback)只是让出时间片,不解决线程独占问题。
为什么 postMessage 传大数据会变慢甚至卡死
结构化克隆(Structured Clone)默认对对象做深拷贝,10MB 的 ArrayBuffer 会被完整序列化再反序列化——这本身就在主线程吃 CPU。实测传输 50MB JSON 时,主线程可能冻结 200ms+,用户点击无响应。
- ✅ 正确做法:用可转移对象(Transferable)跳过拷贝,直接移交内存所有权
worker.postMessage({ data: largeArrayBuffer }, [largeArrayBuffer])- ⚠️ 注意:
largeArrayBuffer在主线程中立即变为ArrayBuffer(0),不能再读写 - ❌ 错误示范:
worker.postMessage({ callback: () => {} })—— 函数无法克隆,直接抛DataCloneError
Worker 中不能调用哪些 API,以及替代方案
Worker 线程没有 window、document、localStorage、fetch(部分浏览器支持,但非标准),也不能操作 DOM。常见误用和解法:
-
fetch:现代 Worker 支持,但需确认目标浏览器(Chrome 120+、Firefox 115+),否则用self.fetch会静默失败 -
JSON.parse/JSON.stringify:安全可用,但大文本解析仍耗时,可考虑流式解析库(如jsonc-parser的 worker 版本) - DOM 操作:完全禁止。渲染结果必须是纯数据(如 SVG 字符串、Canvas 像素数组、React 虚拟 DOM 对象),交由主线程绘制
- 定时器:
setTimeout、setInterval可用,但requestAnimationFrame不可用——Worker 没有帧概念
如何优雅降级:当浏览器不支持 Worker 时
不是所有环境都支持 Web Workers(如某些 WebView、旧版 Safari)。硬报错或白屏不可取,应主动检测并回退:
- 检测方式:
if (typeof Worker === 'undefined') { /* fallback */ } - 回退策略不是简单“同步执行”,而是拆分任务 +
requestIdleCallback控制节奏 - 例如处理 10 万条数据:每 1000 条切一个微任务,用
queueMicrotask或Promise.resolve().then()防止单次阻塞超过 16ms - 避免在降级路径里重复引入
mustache.js或chart.xkcd全量包——按需加载精简版逻辑
主线程与 Worker 的通信设计容易被忽略的关键点
很多人只写一次 postMessage + onmessage,但真实场景需要多任务并发、超时控制、错误隔离:
- 每个请求配唯一
id,Worker 返回时带上该id,主线程用Map匹配 Promise - Worker 内部不要用全局变量缓存状态——多个
postMessage请求会互相污染 - 加超时保护:
setTimeout(() => reject('timeout'), 5000),防止 Worker 挂起不响应 - Worker 报错时,
self.onerror捕获不到异步错误(如Promise拒绝),必须在try/catch中显式postMessage({ error })
真正难的不是启动一个 Worker,而是让通信链路具备错误恢复能力、内存可控、且不把主线程变成消息中转站。多数卡顿问题,根源不在计算本身,而在消息堆积或数据拷贝失控。

















