一、postMessage() 传递数据的机制
1. 默认行为:结构化克隆(Structured Clone)
当我们用 worker.postMessage(data) 发送数据时,浏览器默认会:
- 对数据进行结构化克隆(Structured Clone),即 深拷贝;
- 该拷贝包括对象、数组、Map、Set、TypedArray 等;
- 不会复制原对象的引用关系;
- 拷贝后的新对象在 Worker 中独立存在。
示例:
const obj = { a: 1, b: { c: 2 } };
worker.postMessage(obj);
Worker 收到的对象是一个全新的深拷贝。
修改其中的字段不会影响主线程的原对象。
2. 性能代价
结构化克隆会产生内存复制,数据越大性能损耗越高。
- 小数据(几 KB 以内) → 几乎无感;
- 中大型数据(几 MB 或更多) → 显著延迟;
- 超大数据(图像、音视频缓冲、模型权重等) → 不可接受。
二、Transferable 对象与零拷贝机制
1. Transferable 是什么?
Transferable 是一种所有权转移(ownership transfer)机制: - 不复制数据,而是直接把内存使用权交给接收方; - 零拷贝(Zero-Copy); - 传递后原对象在发送端会变为 "detached"(不可用)。
2. 可被 Transfer 的类型包括:
ArrayBufferMessagePortOffscreenCanvas- 部分 WebAssembly 内存对象
3. 使用方式
postMessage() 的第二个参数用于指定可转移对象数组:
worker.postMessage(buffer, [buffer]);
等价于: > 把 buffer 这个 ArrayBuffer 的所有权转移到 Worker。
三、Transferable 与深拷贝的对比
| 对比项 | 结构化克隆(默认) | Transferable(零拷贝) |
|---|---|---|
| 是否复制内存 | ✅ 会(深拷贝) | ❌ 不会(转移所有权) |
| 性能 | 慢(取决于数据量) | 快(常数级) |
| 发送后可用性 | 原数据仍可访问 | 原数据变为 detached |
| 数据安全性 | 双方各有副本 | 单一所有权 |
| 适合场景 | 小型任务、简单通信 | 大数据(如视频帧、音频流、模型推理) |
四、从 Worker 传回主线程:可否也用 Transferable?
✅ 可以,而且非常常见。
Worker 可以像主线程一样,用 postMessage()
的第二个参数将数据"零拷贝"传回:
// worker.js
self.onmessage = e => {
const buffer = e.data;
const view = new Uint8Array(buffer);
for (let i = 0; i < view.length; i++) {
view[i] *= 2; // 数据处理
}
// ✅ Transferable 回主线程
self.postMessage(buffer, [buffer]);
};
主线程:
worker.onmessage = e => {
const view = new Uint8Array(e.data);
console.log(view); // [2,4,6,8]
};
效果: - 零拷贝往返; - 所有权被从主线程 → Worker → 主线程; - 性能几乎为原生内存操作。
五、Transferable 的工作原理图
主线程 Worker
│ │
│ postMessage(buffer,[buffer]) ─────► 所有权转移
│ │ buffer 可用
│ buffer → detached │
│ │ 处理数据
│◄──── postMessage(buffer,[buffer]) 所有权转回
│ buffer 可用 │ buffer → detached
🔁 所有权在两个线程间来回"传递",而不是复制。
六、可以反复来回传递(循环复用)
高性能场景(如实时数据流)通常使用内存循环复用: - 只创建一次
ArrayBuffer; - 反复在主线程与 Worker 间转移所有权; - 避免反复分配和
GC 压力。
示例:
// main.js
let buffer = new ArrayBuffer(4096);
const worker = new Worker('worker.js');
function loop() {
worker.postMessage(buffer, [buffer]);
}
worker.onmessage = e => {
buffer = e.data; // 拿回所有权
console.log('processed', new Uint8Array(buffer)[0]);
loop(); // 再次传回 Worker
};
loop();
// worker.js
self.onmessage = e => {
const view = new Uint8Array(e.data);
for (let i = 0; i < view.length; i++) view[i]++;
self.postMessage(view.buffer, [view.buffer]);
};
✅ 极高性能,无拷贝、可复用、适合流式任务。
七、总结核心问答
| 你的问题 | 答案总结 |
|---|---|
| 🟦 postMessage 默认是深拷贝还是浅拷贝? | 默认使用 结构化克隆算法(深拷贝) |
| 🟦 深拷贝是否自动?需要自己实现吗? | 自动进行,不可关闭 |
| 🟦 Transferable 怎么用? | 在 postMessage() 第二个参数中传数组:[buffer] |
| 🟦 用 Transferable 有没有劣势? | 原数据会被 “detached”,不能再用(所有权被转移) |
| 🟦 Worker 处理完后能用 Transferable 发回主线程吗? | ✅ 完全可以,也是推荐做法 |
| 🟦 零拷贝能往返循环复用吗? | ✅ 能,适合高性能任务(音视频、AI、图像处理) |
八、关键术语复习
| 术语 | 含义 |
|---|---|
| 结构化克隆(Structured Clone) | 浏览器的深拷贝算法,用于跨线程传值 |
| Transferable | 一种允许“零拷贝”的内存所有权转移机制 |
| detached buffer | 被转移所有权后失效的 ArrayBuffer |
| zero-copy | 直接转移内存而不复制的高性能通信方式 |
| ownership transfer | Transferable 的核心机制,本质是资源交接 |
九、实践建议
| 场景 | 推荐方式 |
|---|---|
| 小数据(简单通信) | 默认 postMessage(data) |
| 大数据(图像/音频流) | 使用 Transferable |
| 往返计算(高频任务) | 使用循环 Transferable |
| 多线程共享数据 | 考虑 SharedArrayBuffer(共享内存,不转移) |
✨ 十、总结一句话
Web Worker 默认深拷贝传值,但 Transferable 提供了零拷贝内存转移。
Transferable 可双向使用,实现高性能"主线程 ↔ Worker"数据流循环。