跨域窗口间传递特殊流式描述符对象时属性丢失,本质是postMessage仅支持结构化克隆,无法处理函数、getter/setter、不可枚举属性、Blob/ReadableStream等;应提取POJO快照重建、用Transferable传二进制数据、通过MessageChannel建立协议化流式通信。

跨域窗口间传递带有特殊流式描述符对象时,反序列化后属性丢失,本质是 JavaScript 对象序列化机制的天然限制——postMessage 仅支持结构化克隆(Structured Clone Algorithm),它不处理函数、undefined、Symbol、Promise、Error、Date(部分环境)、RegExp、Map/Set(旧版浏览器)、以及任何含内部状态或不可枚举描述符(如 Object.defineProperty 设置的 writable: false、configurable: false、enumerable: false)的对象。
这类“特殊流式描述符对象”通常指:
- 含
getter/setter的属性 - 使用
Object.defineProperty显式定义的非可枚举、不可写属性 - 原型链上挂载的方法或访问器
-
Blob、File、ReadableStream等原生流对象(它们本身不可被结构化克隆)
这些内容在 postMessage 传输过程中会被静默忽略或降级为 {} 或 null,导致反序列化后“属性丢失”。
明确哪些内容无法跨窗口直接传递
以下类型在 postMessage 中不会被克隆,也不会报错,而是被丢弃或替换成空值:
- 函数(包括箭头函数、方法、getter/setter 内部逻辑)
-
undefined、Symbol、BigInt(部分环境) -
Error实例(只传空对象或仅部分字段) -
Date对象(部分老环境会变成字符串或丢失) -
RegExp(变成空对象) -
Map/Set/WeakMap/WeakSet(仅现代浏览器支持有限克隆) -
Blob、File、ReadableStream、WritableStream(完全不支持) - 带 `enumerable: false` 或 `writable: false` 的属性(若未显式 `JSON.stringify` 序列化,就不可见)
用可序列化的数据格式重建对象
不要试图传原始对象,而应提取其可安全克隆的状态快照,再在目标窗口重建实例:
- 在发送前,调用自定义
toJSON()方法,返回纯 POJO(Plain Old JavaScript Object) - 例如:
streamDescriptor.toJSON() → { type: 'audio', format: 'mp3', bitrate: 128000, durationMs: 124500 } - 接收方用该数据 new 一个新实例,或调用工厂函数还原逻辑行为
- 避免依赖原型方法,把行为逻辑抽离为独立工具函数(可跨窗口共享)
利用 Transferable 对象传递二进制/流数据
若对象中包含 ArrayBuffer、MessagePort 或 OffscreenCanvas 等可转移资源,可用 postMessage(data, transfer) 的 transfer list 高效移动(而非拷贝):
-
ReadableStream本身不可转移,但可先读取为ArrayBuffer或Uint8Array,再转移 - 示例:
port.postMessage({ buffer }, [buffer]);——buffer在源窗口立即失效,目标窗口独占持有 - 适合大块流式数据(如音频帧、视频 chunk),但需自行管理分片与重组逻辑
通过 MessageChannel 建立双向通道并封装协议
对于需要多次交互、状态同步或流式响应的场景,不依赖单次 postMessage,而是建立长期通信通道:
- 主窗口创建
new MessageChannel(),将port2传给子窗口(iframe.contentWindow.postMessage(..., [port2])) - 双方监听
port1.onmessage/port2.onmessage,约定轻量协议(如{ type: 'STREAM_INIT', id: 'a1b2', meta: {...} }) - 后续用该端口持续推送序列化后的 chunk 数据,接收方按协议组装并触发本地流对象(如
new ReadableStream({ start() { ... } })) - 避免把“流描述符”当数据传,而是把它当作控制指令来驱动两端各自创建对应流
不复杂但容易忽略。

















