使用通道消息

注意:此功能在 Web Workers 中可用。

通道消息 API允许在同一文档附加的两个独立脚本(例如,两个<iframe>元素、主文档和单个<iframe>,或通过SharedWorker的两个文档)直接通信,通过双向通道(或管道)在每个端点有一个端口之间传递消息。

在本文中,我们将探讨这项技术的基础知识。

用例

通道消息主要适用于需要将来自其他网站的功能通过 iframe 嵌入到主界面的社交网站,例如游戏、通讯录或带有个性化音乐选择的音频播放器。当它们作为独立单元运行时,一切都还好,但当您想要主网站和<iframe>元素之间,或不同<iframe>元素之间进行交互时,就会出现困难。例如,如果您想从主网站向通讯录添加联系人,将游戏中的高分添加到主个人资料,或者从音频播放器添加新的背景音乐选择到游戏中,该怎么办?由于 Web 使用的安全模型,使用传统 Web 技术来实现这些功能并不容易。您需要考虑来源之间是否相互信任,以及消息如何传递。

另一方面,消息通道可以提供一个安全通道,允许您在不同的浏览上下文之间传递数据。

注意:有关更多信息和想法,规格中“Web 上的对象能力模型基础端口”部分是一篇有用的读物。

简单示例

为了帮助您入门,我们在 GitHub 上发布了一些演示。首先,请查看我们的通道消息基本演示(也可以在线运行),它展示了一个页面和嵌入的<iframe>之间非常简单的单消息传输。

其次,请查看我们的多消息演示(可以在线运行),它展示了一个稍微复杂的设置,可以在主页面和 IFrame 之间发送多条消息。

在本文中,我们将重点介绍后一个示例,该示例看起来像

Demo with "Hello this is my demo" sent as five separate messages. The messages are displayed as a bulleted list.

创建通道

在演示的主页面中,我们有一个简单的表单,其中包含一个文本输入框,用于输入要发送到<iframe>的消息。我们还有一个段落,稍后将用它来显示将从<iframe>收到的确认消息。

js
const input = document.getElementById("message-input");
const output = document.getElementById("message-output");
const button = document.querySelector("button");
const iframe = document.querySelector("iframe");

const channel = new MessageChannel();
const port1 = channel.port1;

// Wait for the iframe to load
iframe.addEventListener("load", onLoad);

function onLoad() {
  // Listen for button clicks
  button.addEventListener("click", onClick);

  // Listen for messages on port1
  port1.onmessage = onMessage;

  // Transfer port2 to the iframe
  iframe.contentWindow.postMessage("init", "*", [channel.port2]);
}

// Post a message on port1 when the button is clicked
function onClick(e) {
  e.preventDefault();
  port1.postMessage(input.value);
}

// Handle messages received on port1
function onMessage(e) {
  output.innerHTML = e.data;
  input.value = "";
}

我们首先通过使用MessageChannel()构造函数创建一个新的消息通道。

当 IFrame 加载完成后,我们为我们的按钮注册一个onclick处理程序,并为MessageChannel.port1注册一个onmessage处理程序。最后,我们使用window.postMessage方法将MessageChannel.port2传输到 IFrame。

让我们更详细地研究一下iframe.contentWindow.postMessage行的工作原理。它接受三个参数

  1. 要发送的消息。对于这个初始的端口传输,消息可以是一个空字符串,但在本例中它被设置为'init'
  2. 消息要发送到的源。*表示“任何源”。
  3. 一个对象,其所有权被转移到接收浏览上下文。在这种情况下,我们将MessageChannel.port2传输到 IFrame,以便它可以使用它与主页面进行通信。

当我们的按钮被点击时,我们阻止表单正常提交,然后通过MessageChannel将我们在文本输入框中输入的值发送到 IFrame。

在 IFrame 中接收端口和消息

<iframe>元素中,我们有以下 JavaScript

js
const list = document.querySelector("ul");
let port2;

// Listen for the initial port transfer message
window.addEventListener("message", initPort);

// Setup the transferred port
function initPort(e) {
  port2 = e.ports[0];
  port2.onmessage = onMessage;
}

// Handle messages received on port2
function onMessage(e) {
  const listItem = document.createElement("li");
  listItem.textContent = e.data;
  list.appendChild(listItem);
  port2.postMessage(`Message received by IFrame: "${e.data}"`);
}

当通过window.postMessage方法从主页面接收到初始消息时,我们运行initPort函数。此函数保存传输的端口并注册一个onmessage处理程序,该处理程序将在每次通过我们的MessageChannel传递消息时被调用。

当收到来自主页面的消息时,我们创建一个列表项并将其插入到无序列表中,将列表项的textContent设置为事件的data属性(其中包含实际消息)。

接下来,我们通过调用最初传输到 iframe 的MessageChannel.port2上的MessagePort.postMessage,将一条确认消息通过消息通道发送回主页面。

在主页面中接收确认

回到主页面,现在让我们看一下onmessage处理程序函数。

js
// Handle messages received on port1
function onMessage(e) {
  output.innerHTML = e.data;
  input.value = "";
}

当从 IFrame 收到确认原始消息已成功接收的消息时,这会将确认输出到段落,并清空文本输入框,以便发送下一条消息。

规范

规范
HTML
# message-channels
HTML
# message-ports

浏览器兼容性

api.MessageChannel

api.MessagePort

另见