跳到主要内容

消息频道 ( channel messaging )

备注

该部分是补充部分知识,用于介绍 Channel Message,在调用程序中被用作创建宏任务以让出当前执行的线程占用。

信息

若对该部分知识比较熟悉可跳过忽略

Channel Messaging API 允许两个运行在同一文档的不同浏览上下文(比如两个 iframe ,或者文档主体和一个 iframe,使用 SharedWorker 的两个文档,或者两个 worker)中的独立脚本直接进行通信,在每端使用一个端口( port )通过双向频道( channel )或管道( pipe )像彼此传递消息。

信息

是同一个 new MessageChannel() 实例的两个端口直接的消息传递。

一、概念和用法

使用 MessageChannel() 构造函数来创建信息频道。一旦创建,可以通过 MessageChannel.port1MessageChannel.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. 实例属性

构造函数返回的实例上有两个只读属性:

  • port1
  • port2

两个属性是两个 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 参数含义相同
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;
}

2. 实例方法 start()

MessagePort 接口的 start() 方法开始发送该端口中的消息列队。只有在使用 EventTarget.addEventListener 时才需要此方法;使用 onmessage 事件时已隐含调用该方法。

ts showLineNumbers title="语法" 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.postMessageMessageChannel
通信模式单向或是双向(需手动管理)原生双向通道 (两个端口自动配对)
持久性临时消息(无状态)直接化通道 (可复用)
复杂度简单(直接发送/接收)中等(需要创建通道并管理端口)
适用场景简单的一次性消息高频/复杂交互(如 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