跳到主要内容

消息频道 ( 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 属性发送数据。

构造函数​

MessageChannel 接口的 MessageChannel() 构造函数返回一个新的 MessageChannel 对象,其中包含两个新的 MessagePort 对象。

new MessageChannel();

无需参数,返回一个新的 MessageChannel 对象。

实例属性​

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

  • port1
  • port2

两个属性是两个 MessagePort 对象,连接到发起 channel 上下文的端口。

使用示例​

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 的两个端口,可以从一个端口发送消息,并在消息到达的另一个端口监听他们。

::::tip 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

::::

实例方法 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;
}

实例方法 start()​

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

ts showLineNumbers title="语法" start()

实例方法 close()​

MessagePort 接口的 close() 方法断开端口连接,使其不再处于活动状态。这将停止将该端口发送消息。

语法
close();

事件 message​

MessagePort 对象上的 message 事件在有消息到达该消息频道时触发。

此事件不可取消,也不会冒泡。

语法
// 多事件注册监听
addEventListener('message', event => {});

// 单事件注册监听
onmessage = event => {};

事件 messageerror​

MessagePort 对象的 messageerror 事件在接受到无法反序列化的消息时触发。

此消息不可取消,不可冒泡。

语法
// 多事件注册监听
addEventListener('messageerror', event => {});

// 单事件注册监听
onmessageerror = event => {};

与 window.postMessage 区别​

特性window.postMessageMessageChannel
通信模式单向或是双向(需手动管理)原生双向通道 (两个端口自动配对)
持久性临时消息(无状态)直接化通道 (可复用)
复杂度简单(直接发送/接收)中等(需要创建通道并管理端口)
适用场景简单的一次性消息高频/复杂交互(如 Web Workers 、 多上下文协作)
端口传递❌ 不支持✅ 可通过 postMessage 传递端口
性能每次调用独立传输复用统一通道

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 验证来源

二者的联系​

  • 协同工作 : MessageChannel 常通过 postMessage 传递端口 建立的连接 :
    // 父页面向 iframe 传递 port2
    iframe.contentWindow.postMessage('init', '*', [channel.ports2]);
  • 共享底层机制 : 两者都是基于 HTML 5 跨文档消息机制传递 API , 使用相同的安全模式( origin 校验)和数据序列话(结构化克隆算法)
  • 互不场景 :
    • 用 postMessage 发起链接 ➞ 用 MessageChannel 维持长会话
    • 简单指令用 postMessage ,复杂数据流用 MessageChannel