消息频道 ( channel messaging )
该部分是补充部分知识,用于介绍 Channel Message,在调用程序中被用作创建宏任务以让出当前执行的线程占用。
若对该部分知识比较熟悉可跳过忽略
Channel Messaging API 允许两个运行在同一文档的不同浏览上下文(比如两个 iframe ,或者文档主体和一个 iframe,使用 SharedWorker 的两个文档,或者两个 worker)中的独立脚本直接进行通信,在每端使用一个端口( port )通过双向频道( channel )或管道( pipe )像彼此传递消息。
是同一个 new MessageChannel() 实例的两个端口直接的消息传递。
一、概念和用法
使用 MessageChannel() 构造函数来创建信息频道。一旦创建,可以通过 MessageChannel.port1 和 MessageChannel.port2 属性访问频道的两个端口(这两个属性都会返回 MessagePort 对象)。
创建频道的应用程序使用 port1 ,在另一端的程序使用 port2。向另一个端口发送消息,可携带 2 个参数(需要传递的信息,要传递所有权的对象,在这种情况下是端口自身)调用 window.postMessage 方法将端口传递到另一个浏览器上下文。
当这些可转移的对象被传递后,他们就会从所属的上下文消失了。比如一个端口,一旦被发送,在原本的上下文九不可再用了。
另一个上下文可以利用 onmessage 监听消息,并使用事件的 data 属性来获取消息内容。通过 MessagePort.postMessage 将消息发送回原始文档进行响应。
当想要停止通过通道发送消息时,可以通过调用 MessagePort.close 来关闭端口。
二、MessageChannel
Channel Messaging API 的 MessageChannel 接口允许创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据。
1. 构造函数
MessageChannel 接口的 MessageChannel() 构造函数返回一个新的 MessageChannel 对象,其中包含两个新的 MessagePort 对象。
new MessageChannel();
无需参数,返回一个新的 MessageChannel 对象。
2. 实例属性
构造函数返回的实例上有两个只读属性:
port1port2
两个属性是两个 MessagePort 对象,连接到发起 channel 上下文的端口。
3. 使用示例
const { port1, port2 } = new MessageChannel();
// 注册处理器
port1.onmessage = () => {
console.log('我是一个回调方法');
};
setTimeout(() => {
// 我向端口 1 发送了消息,以让其注册的回调方法被执行
port2.pushMessage(null);
}, 12580);
postMessage会将消息放入 宏任务列队- 下一次事件循环时,
onmessage被执行 - 这个过程 比
setTimeout(fn, 0)更快,更可控
三、MessagePort
Channel Messaging API 的 MessagePort 接口代表 MessageChannel 的两个端口,可以从一个端口发送消息,并在消息到达的另一个端口监听他们。
MessagePort 是一个可转移对象可转移对象 ( Transferable object ) 拥有自己的资源对象,这些资源可以从一个上下文 转移到 另一个,确保资源一次仅在一个上下文可用。传输后,原始对象不可再用;它不再指向转移后的资源,并且读取或写入改对象的尝试都将抛出异常。
MessagePort 实例需要通过 :
- 在 Web Workers 中 :
window.postMessage - 在 Service Workers 中 :
Client.postMessage()或ServiceWorker.postMessage() - 在 Shared Workers 中 :
SharedWorker.port.postMessage()或通过connect事件 - 在 Broadcast Channel API 中 : 结合
Broadcast Channel协调,但实际传递端口还是需要postMessage - 在 Service Workers 的 MessageChannel 中 : 实际上还是通过
postMessage
1. 实例方法 postMessage()
从端口发送一条消息,并且可选是否将对象的所有权交给其他浏览器上下文。
// 传递消息
postMessage(message);
// 发送消息时包含一个要转让所有权的可转移对象的可选数组
postMessage(message, transfer);
// 传递可转移对象的向后兼容写法,方便以后添加更多的属性
postMessage(message, options);
message: 需要通过 channel 发送的消息。可以是任何基础数据类型。多个数据项可以作为数组发送transfer(可选) : 一个包含要转让所有权的可转移对象的可选的数组,这些对象的所有权转移到接收方,发送发不再使用他们。这些可转移对象应附加到消息中;否则他们将被转移,但是上在接收方无法访问options(可选) : 包含以下属性的可选对象:transfer(可选) : 与transfer参数含义相同
- 主上下文
- iframe 配置
const channel = new MessageChannel(); // channel 实例
const para = document.querySelector('p'); // 页面元素
const ifr = document.querySelector('iframe'); // iframe 框架
const otherWindow = ifr.contentWindow; // iframe 框架的上下文
// iframe 加载后注册
ifr.addEventListener('load', iframeLoaded, false);
function iframeLoaded() {
// 在该上下文加载 iframe 后,向 iframe 转移 MessagePort
otherWindow.postMessage('传输信息端口', '*', [channel.port2]);
}
channel.port1.onmessage = handleMessage;
function handleMessage(e) {
para.innerHTML = e.data;
}
// 在 iframe 中...
window.addEventListener('message', event => {
// 被转移来的 MessagePort
const messagePort = event.ports?.[0];
// 通过 MessagePort 向主上下文发送消息
messagePort.postMessage('你好,来自 iframe!');
});
2. 实例方法 start()
MessagePort 接口的 start() 方法开始发送该端口中的消息列队。只有在使用 EventTarget.addEventListener 时才需要此方法;使用 onmessage 事件时已隐含调用该方法。
- 语法
- 示例
ts showLineNumbers title="语法" start()
channel.port1.addEventListener('message', handleMessage, false);
function handleMessage(e) {
para.innerHTML = e.data;
textInput.value = '';
}
channel.port1.start();
3. 实例方法 close()
MessagePort 接口的 close() 方法断开端口连接,使其不再处于活动状态。这将停止将该端口发送消息。
close();
4. 事件 message
MessagePort 对象上的 message 事件在有消息到达该消息频道时触发。
此事件不可取消,也不会冒泡。
// 多事件注册监听
addEventListener('message', event => {});
// 单事件注册监听
onmessage = event => {};
5. 事件 messageerror
MessagePort 对象的 messageerror 事件在接受到无法反序列化的消息时触发。
此消息不可取消,不可冒泡。
// 多事件注册监听
addEventListener('messageerror', event => {});
// 单事件注册监听
onmessageerror = event => {};
四、与 window.postMessage 区别
| 特性 | window.postMessage | MessageChannel |
|---|---|---|
| 通信模式 | 单向或是双向(需手动管理) | 原生双向通道 (两个端口自动配对) |
| 持久性 | 临时消息(无状态) | 直接化通道 (可复用) |
| 复杂度 | 简单(直接发送/接收) | 中等(需要创建通道并管理端口) |
| 适用场景 | 简单的一次性消息 | 高频/复杂交互(如 Web Workers 、 多上下文协作) |
| 端口传递 | ❌ 不支持 | ✅ 可通过 postMessage 传递端口 |
| 性能 | 每次调用独立传输 | 复用统一通道 |
1. window.postMessage
直接向目标窗口发送消息:
// 发送发
targetWindow.postMessage(data, 'https://target-domain.com');
// 接收方
window.addEventListener('message', event => {
if (event.origin !== 'https://trusted-domain.com') return;
console.log('Received:', event.data);
});
- 简单直接 : 适合一次性通知(如初始化参数)
- 无状态 : 每次调用独立存在
- 需显式指定目标 : 依赖
targetWindow引用(如iframe.contentWindow) - 安全控制 : 通过
origin验证来源
2. 二者的联系
- 协同工作 :
MessageChannel常通过postMessage传递端口 建立的连接 :// 父页面向 iframe 传递 port2
iframe.contentWindow.postMessage('init', '*', [channel.ports2]); - 共享底层机制 : 两者都是基于 HTML 5 跨文档消息机制传递 API , 使用相同的安全模式( origin 校验)和数据序列话(结构化克隆算法)
- 互不场景 :
- 用
postMessage发起链接 ➞ 用MessageChannel维持长会话 - 简单指令用
postMessage,复杂数据流用MessageChannel
- 用