web worker 的 postmessage 是结构化克隆加异步事件投递,传函数、undefined、symbol、dom节点、循环引用对象会报datacloneerror;主线程需先绑定onmessage再postmessage;worker内推荐用self.postmessage;传arraybuffer需通过transfer列表实现零拷贝。

Web Worker 的 postMessage 不是“发个消息就完事”,它本质是一次结构化克隆 + 异步事件投递,数据传过去就是副本,改了也白改;想零拷贝传大数组?得手动加 transfer 参数。
postMessage 传什么数据会出错?
结构化克隆算法明确不支持这些类型,直接传会导致静默失败或报错:TypeError: DataCloneError:
-
function、undefined、Symbol - DOM 节点(比如
document.getElementById('x')) - 含有循环引用的对象(如
obj.a = obj) -
RegExp和Date虽然支持,但反序列化后不是同一实例(new Date() !== receivedDate)
安全做法:只传 plain object、array、number、string、ArrayBuffer、Blob;复杂结构先 JSON.stringify 再传(但会丢掉原型和函数)。
主线程怎么正确监听 Worker 返回的消息?
必须在 new Worker() 后立即绑定 onmessage 或 addEventListener('message', ...),否则可能漏掉首条消息——Worker 启动快,主线程监听慢,消息就丢了:
const worker = new Worker('calc.js');
// ✅ 正确:监听器在 postMessage 前就位
worker.onmessage = function(e) {
console.log('收到结果:', e.data);
};
// ✅ 也可以用 addEventListener(更推荐,支持多次绑定)
worker.addEventListener('message', e => {
if (e.data.type === 'done') {
document.getElementById('status').textContent = '完成';
}
});
// ❌ 错误:先 postMessage 再监听,首条消息大概率收不到
worker.postMessage({ cmd: 'start' }); // ← 这条可能被丢
worker.onmessage = ...;
Worker 内怎么发消息回主线程?用 self.postMessage 还是 postMessage?
两者在 Worker 全局作用域里等价,但显式写 self.postMessage 更清晰,避免和主线程代码混淆:
-
self指向WorkerGlobalScope,是 Worker 的全局对象 -
postMessage是self的方法,省略self.属于隐式调用,容易让人误以为是主线程 API - Worker 中不能用
window、document,所以window.postMessage会报ReferenceError
Worker 脚本(calc.js)示例:
self.onmessage = function(e) {
const { data } = e.data;
const result = data.reduce((a, b) => a + b, 0);
// ✅ 推荐写法,语义明确
self.postMessage({ type: 'result', value: result });
// ⚠️ 可以,但不推荐(易混淆)
// postMessage({ type: 'result', value: result });
};
传 ArrayBuffer 怎么避免拷贝?transfer 列表别漏掉
传大块二进制数据(如图像像素、音频 buffer)时,默认 postMessage(data) 会完整克隆,内存翻倍、卡顿明显。要用 transfer 把所有权移交,实现零拷贝:
- 主线程发送时,第二个参数必须是包含该
ArrayBuffer的数组:worker.postMessage(data, [data.buffer]) - 传完后,主线程的
data.buffer立即变为detached,再访问会报TypeError: ArrayBuffer is detached - Worker 收到后,
e.data.buffer就是原 buffer,可直接用new Uint8Array(e.data.buffer)
错误示范(没加 transfer):
const ab = new ArrayBuffer(1024 * 1024);
worker.postMessage({ pixels: new Uint8Array(ab) }); // ❌ 拷贝 1MB
正确写法:
const ab = new ArrayBuffer(1024 * 1024);
const view = new Uint8Array(ab);
worker.postMessage({ pixels: view }, [ab]); // ✅ 零拷贝,ab 归 Worker 所有
这个细节一旦漏掉,性能优化就白做了——看着用了 Worker,实际还是在主线程扛着拷贝压力。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











