登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

浏览器 MessageChannel 如何把端口交给 iframe

来源:17golang原创

时间:2026-09-15 09:53:59 396浏览 收藏

主页面想把 MessageChannel 的端口交给 iframe,关键不是把 port2 当普通数据传过去,而是在 iframe.contentWindow.postMessage() 的第三个参数中转移它。父页面保留 port1,iframe 从初始 message 事件的 event.ports[0] 接管 port2,之后双方直接通过端口通信。

要点速览
  • 等 iframe 的 load 事件后再发送端口,避免接收端监听器尚未建立。
  • targetOrigin 要写成 iframe 的真实源;接收端还要检查 event.originevent.source
  • 端口转移后,发送方不再使用原来的 port2,业务消息统一从父页面的 port1 发出。

先分清两次消息:交付端口和传业务

这里其实有两条不同的链路。第一次是父页面调用 contentWindow.postMessage("init", targetOrigin, [port2]),它把端口所有权交给 iframe;第二次开始,父页面调用 port1.postMessage(data),iframe 调用接管后的端口发送回执。postMessage 的 transfer 数组只负责转移所有权,端口必须同时出现在接收事件的 event.ports 中。

阶段发送方接收位置要确认的点
初始化父页面 Windowiframe 的 Window messageevent.ports[0] 存在
业务通信父页面 port1iframe port2 的 message收到业务数据并能回 ack
回执iframe port2父页面 port1 的 message回执内容和请求对应

父页面:在 iframe 加载后转移 port2

父页面只保留 port1。示例用一个固定的 FRAME_ORIGIN,生产代码不要为了省事使用 *;如果 iframe 的协议、域名或端口变化,应当显式更新这个值。

const iframe = document.querySelector("#worker-frame");
const channel = new MessageChannel();
const FRAME_ORIGIN = "https://widget.example.com";

// 父页面只监听并使用 port1,port2 会在初始化时转移给 iframe。
channel.port1.onmessage = (event) => {
  if (event.data?.type === "widget-ready") {
    document.querySelector("#status").textContent = "iframe 已接通";
  }
};

// 等 iframe 完成加载,避免它错过接收初始化端口的 message 事件。
iframe.addEventListener("load", () => {
  iframe.contentWindow.postMessage(
    { type: "channel-init" },
    FRAME_ORIGIN,
    [channel.port2]
  );
}, { once: true });

function sendToFrame(payload) {
  // 端口已经转移后,业务消息从父页面保留的 port1 发出。
  channel.port1.postMessage({ type: "request", payload });
}
浏览器 MessageChannel 父页面保留 port1 并通过 postMessage transfer 转移 port2 给 iframe 的结构示意图
图1:父页面端口分工与 iframe 初始化交付的结构示意图,不是实际运行截图。

iframe:校验来源后接管 event.ports[0]

iframe 首先接收的是 Window 的 message,不是端口的业务消息。接收端要先验证来源,再确认消息类型和端口数量,最后将 event.ports[0] 保存下来。使用 onmessage 时浏览器会自动开始派发队列;如果改成 addEventListener,再补充调用 port.start() 更清晰。

const PARENT_ORIGIN = "https://portal.example.com";
let parentPort;

window.addEventListener("message", (event) => {
  // 先拒绝未知窗口和未知来源,避免把端口交给不可信页面。
  if (event.origin !== PARENT_ORIGIN || event.source !== window.parent) {
    return;
  }
  if (event.data?.type !== "channel-init" || event.ports.length !== 1) {
    return;
  }

  parentPort = event.ports[0];
  parentPort.onmessage = (messageEvent) => {
    if (messageEvent.data?.type !== "request") return;
    // 这里处理业务数据,并用同一端口回传接通确认。
    parentPort.postMessage({ type: "widget-ready", received: true });
  };
});
iframe 校验 event.origin 与 event.source 后从 event.ports[0] 接管 MessagePort 并回传确认的结构示意图
图2:iframe 端从初始 Window 消息接管端口,再通过端口回传结果的结构示意图,不是实际运行截图。

端口交不过去时,按这张清单排查

最常见的问题不是 API 不支持,而是链路中的对象和时机搞混。先看初始化消息是否抵达,再看端口是否存在,最后才检查业务消息。

现象优先检查修复方向
iframe 没有收到 messageload 时机与 targetOrigin等加载完成,并核对协议、主机、端口
event.ports 为空是否把 port2 放入第三参数使用 [channel.port2],不要只把它写进普通数据
初始化后父页面继续用 port2所有权已转移父页面只用 port1,iframe 只用接管的端口
业务消息没有回执iframe 是否绑定端口 onmessage保存 event.ports[0] 后再发送业务消息

常见问题

为什么不能直接访问跨域 iframe 的 document?

同源策略限制了跨源 DOM 访问。postMessage 提供的是受控的跨窗口通信入口,不能把它理解成绕过所有安全检查。

端口转移后还能在父页面调用 port2 吗?

不能把转移后的 port2 当作父页面可继续使用的端口。发送方应保留 port1,接收方使用收到的端口。

什么时候需要调用 port.start()?

使用 onmessage 时启动动作是隐含的;如果用 addEventListener("message", handler),可显式调用 start() 表明开始消费消息队列。

这套写法的判断标准很简单:初始化 Window 消息有一个端口,iframe 能从 event.ports[0] 接管它,之后双方都只使用各自的一端,并且来源校验和回执都能对上。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>