messagechannel 可用于兼容旧浏览器的深克隆,通过 port.postmessage/ onmessage 实现异步结构化克隆,支持 date、map、正则、undefined 和循环引用,但不拷贝函数、不保留原型链,且需运行时检测兼容性并捕获 datacloneerror。

MessageChannel 可以在不支持 structuredClone 的旧版浏览器(如 Chrome undefined、Date、RegExp、Map、Set、ArrayBuffer、Blob、ImageData,还能安全处理循环引用——但无法拷贝函数、Symbol、Promise、WeakMap、WeakSet 等不可转移类型。
为什么选 MessageChannel 而不是 JSON 方法
JSON 方式(JSON.parse(JSON.stringify(obj)))在旧环境中虽快,但有硬伤:
- 丢弃
undefined、function、Symbol属性 - 把
Date变成字符串,RegExp变成空对象 - 遇到循环引用直接报错
TypeError: Converting circular structure to JSON -
Map/Set被转为空对象或数组,丢失键值关系
核心实现:封装一个 Promise 化的 deepClone 函数
利用 MessageChannel 的双端口通信机制,把源对象通过 port1.postMessage() 发出,由 port2.onmessage 接收并 resolve 拷贝结果。注意:该过程必然是异步的,且不能在同步上下文中“等待”。
示例代码:
function deepClone(obj) {return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (event) => resolve(event.data);
port1.postMessage(obj);
});
}
// 使用方式
const original = {
a: 1,
b: new Date('2023-01-01'),
c: /test/gi,
d: new Map([['x', 1]]),
e: undefined,
};
original.self = original; // 循环引用
deepClone(original).then(cloned => {
console.log(cloned.b instanceof Date); // true
console.log(cloned.c.toString()); // "/test/gi"
console.log(cloned.d.size); // 1
console.log(cloned.e === undefined); // true
console.log(cloned.self === cloned); // true
});
兼容性兜底与错误边界处理
并非所有环境都支持 MessageChannel(如某些低版本 WebView 或 IE),需做运行时检测;同时要拦截不可序列化类型的报错(如传入函数时,postMessage 会抛出 DataCloneError)。
- 先检查
typeof window.MessageChannel === 'function' - 用
try/catch包裹postMessage,捕获DataCloneError并降级为浅拷贝或抛出明确提示 - 对
null、基本类型(string/number/boolean)可直接返回,避免无谓通道创建
实际使用中的关键注意事项
这不是“万能同步替代品”,需清醒认知其行为特征:
- 一定是异步的:返回 Promise,不能用于需要同步返回的场景(如 Vue 的
data()函数) - 不拷贝函数:传入含方法的对象时,方法字段会被静默忽略(Chrome 报错,Firefox/Safari 可能静默丢弃)
- 不保留原型链:拷贝后对象的
__proto__是Object.prototype,非原构造器实例 - 性能开销较大:每次调用都新建通道、触发宏任务调度,高频小对象拷贝不如 JSON 快
- 不支持 transferable 列表:不像 Worker 的
postMessage,这里不能传ArrayBuffer的所有权,只是拷贝










