前端跨页面通讯终极指南④:MessageChannel 用法全解析

前言

上一篇介绍了Localstorage 跨页面通讯的方式。在前面的文章中,介绍了多种跨页面通信方式,从适用于同源页面的 BroadcastChannel,到解决跨域的 postMessage。当多个通信进行混杂在一起,使用全局的message事件监听时,会通过各种类型判断消息来源进行处理。

那有没有一种方法,既能实现跨上下文通信,又能像打电话一样,建立起一条专属的、双向的、点对点的私密通道呢?

今天介绍一个方案------MessageChannel API。提供了一种更为优雅和私密的方式,建立起一条点对点的"专线电话",让通信双方可以清晰地、无干扰地对话。

1. MessageChannel 是什么?

Channel Messaging APIMessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据。

核心是创建一个双向通讯的管道 ,这个管道包含两个相互关联的端口------port1port2。数据从 port1 发送,就只能由 port2 接收;反之,port2 发送的数据也只能被 port1 捕获,这种"点对点"的通讯模式,从根源上避免了数据被无关页面拦截的风险。

举个通俗的例子: BroadcastChannel 是"小区广播",所有人都能听到;而 MessageChannel 就是"专线电话",只有两个端口对应的设备能接通。

2. 如何使用

使用 MessageChannel 流程如下:

  1. 创建通道 : 创建一个 MessageChannel 实例,包含两个端口属性:port1port2
js 复制代码
const channel = new MessageChannel();

// channel.port1 和 channel.port2 都是 MessagePort 对象
  1. 监听消息 : 在其中一个端口上设置 onmessage 事件处理器,用于接收来自另一个端口的消息。
js 复制代码
channel.port1.onmessage = (event) => {
    console.log('收到消息:', event.data);
};
  1. 发送消息 : 通过一个端口的 postMessage方法向另一个端口发送数据。
js 复制代码
channel.port2.postMessage('Hello from port2!');
  1. 转移端口所有权MessageChannel 的威力在于可以将一个端口发送到另一个浏览上下文(例如 iframe)。这需要通过 window.postMessage 的第三个参数 transfer 来实现, 可以直接将端口下发到两个子iframe,直接进行通讯。
js 复制代码
// 假设 iframe 是我们想要通信的目标
const iframe = document.querySelector('iframe').contentWindow;

// 将 port2 的所有权转移给 iframe
// 转移后,当前页面就不再拥有 port2,只有 iframe 能使用它

iframe.postMessage('init', '*', [channel.port2]);

使用postMessage发送端口会出现ports:

接收方(iframe)可以在其 message 事件中获取到这个端口,开始通信。

3. 实践场景

下面使用MessageChannel进行父子双向、兄弟通讯进行说明。

3.1 父子双向通讯

父页面引入一个 iframe,创建MessageChannel,初始化时将其中一个port1使用postMessage传递,后面直接通过port进行双向通讯。

步骤1:父页面(发送端口+通讯逻辑)

ini 复制代码
// 1. 创建 MessageChannel 实例,生成两个端口
const channel = new MessageChannel();
const { port1, port2 } = channel;

// 2. 获取 iframe 元素,监听加载完成事件
const iframe = document.getElementById('myIframe');
iframe.onload = () => {
  // 3. 向 iframe 传递 port2(关键:只有传递端口后才能通讯)
  iframe.contentWindow.postMessage('init', '*', [port2]);
};

// 4. 监听 port1 接收的消息(来自 iframe)
port1.onmessage = (e) => {
  console.log('父页面收到 iframe 消息:', e.data);
  // 收到消息后回复
  if (e.data === 'hello from iframe') {
    port1.postMessage('hi iframe, I am parent');
  }
};

// 5. 可选:监听错误事件
port1.onerror = (error) => {
  console.error('通讯错误:', error);
  port1.close(); // 出错后关闭端口
};

步骤2:iframe 页面(接收端口+响应逻辑)

ini 复制代码
// 1. 监听父页面发送的初始化消息
window.onmessage = (e) => {
  // 2. 验证消息类型,获取传递的 port2
  if (e.data === 'init' && e.ports.length) {
    const port2 = e.ports[0];

    // 3. 监听 port2 接收的消息(来自父页面)
    port2.onmessage = (msg) => {
      console.log('iframe 收到父页面消息:', msg.data);
    };

    // 4. 向父页面发送消息
    port2.postMessage('hello from iframe');
  }
};

需要注意的是:

  • 父页面通过 postMessage 传递 port2 时,必须将 port2 放在第三个参数(transferList)中,这是 "端口传递"的固定写法;
  • 端口一旦传递,父页面的 port2 就会失效,只能通过 iframe 中的 port2 通讯。

3.2 兄弟通讯

实现思路是: 父页面作为"总机",负责创建 MessageChannel,并将两个端口分别分配给两个 iframe,让它们之间建立起直连专线。

步骤1:父页面代码

js 复制代码
const channel = new MessageChannel();
const frame1 = document.getElementById('frame1').contentWindow;
const frame2 = document.getElementById('frame2').contentWindow;
// 等待两个 iframe 加载完成
let loadCount = 0;
const onLoad = () => {
  loadCount++;
  if (loadCount === 2) {
    // 将 port1 发送给 iframe1
    frame1.postMessage('init', '*', [channel.port1]);
    // 将 port2 发送给 iframe2
    frame2.postMessage('init', '*', [channel.port2]);
    console.log('父页面:专线已建立,端口已分发。');
  }
};
document.getElementById('frame1').onload = onLoad;
document.getElementById('frame2').onload = onLoad;

步骤2:子页面接收port

js 复制代码
let port;
// 1. 接口端口
window.addEventListener('message', (event) => {
  // 确认是父页面发来的初始化消息,并接收端口
  if (event.data === 'init') {
    port = event.ports[0];
    console.log('iframe1:已接收 port1,准备发送消息。');
  }
});
// 2. 发送消息
document.getElementById('sendBtn').onclick = () => {
  if (port) {
    const message = `来自 iframe1 的问候,时间:${new Date().toLocaleTimeString()}`;
    port.postMessage(message);
    console.log('iframe1:消息已发送 ->', message);
  }
};
// 3. 监听 port 消息
port.onmessage = (e) => {
  console.log('页面B收到消息:', e.data);
};

实际效果:

4. 注意事项

  1. 端口传递必须用 transferList

传递端口时,必须将 port 放在 postMessage 的第三个参数(transferList)中,而不是作为第一个参数(data)。错误写法会导致端口无法正常绑定,通讯失效。

  1. 通讯完成后及时关闭端口

不需要通讯时,调用 port.close() 关闭端口,避免内存泄漏。

5. 总结

最后总结一下:MessageChannel 通过创建一个专属的双向通道,解决了点对点通信的需求,唯一不足的是无论是父子还是兄弟通讯都是需要使用postMessage进行传递端口。

相关推荐
华玥作者12 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_13 小时前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠13 小时前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang2015092813 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC14 小时前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务14 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整15 小时前
面试点(网络层面)
前端·网络
VT.馒头15 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
phltxy16 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron070717 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js