{T}
项目内容
课程名称未提供
当前章节浏览器标签页之间的消息通信扩展
知识领域浏览器 API / 前端通信 / 多标签页协作
学习目标理解 postMessage 在跨标签页场景中的使用方式,掌握消息发送、接收、来源校验和双向通信的实现思路
整理时间2026-03-29
资料说明基于课程原文整理,并对术语、代码写法和安全注意事项做规范化处理

一、为什么需要跨标签页通信 必须掌握

1.1 常见业务场景

概念说明

在后台管理系统、统一登录、支付跳转、第三方授权、单点登录等场景中,页面之间经常需要交换状态或通知结果。

例如:

  • 在一个标签页中打开登录页,登录成功后通知原页面刷新状态
  • 在管理后台中打开新标签页,完成某个操作后同步回原页面
  • 在一个域名打开的中转页,将消息发回原始业务页

语法/用法

标签页之间通信,常见方案包括:

  • localStorage + storage 事件
  • BroadcastChannel
  • window.postMessage

注意事项

  • localStorage 事件和 BroadcastChannel 更适合同源页面之间通信。
  • 如果两个页面不在同一个源上,postMessage 往往是更合适的方案。

二、同源通信方案与跨源通信方案的区别 重要

2.1 什么叫同源

概念说明

浏览器中的“同源”指的是协议、域名、端口三者都相同。

例如:

  • http://localhost:5173http://localhost:5173 是同源
  • http://localhost:5173http://localhost:5174 不是同源
  • http://localhost:5173http://tomok.cn:5173 不是同源

注意事项

  • 原文里提到的 localhost 和自定义域名映射,即使都指向本机服务,只要域名不同,也不是同源。
  • “在同一个浏览器里打开”不等于“同源”。

2.2 什么时候选 postMessage

概念说明

如果页面之间不是同源关系,像 BroadcastChannel 这类方案就不能直接跨源使用。这时可以利用浏览器提供的 window.postMessage 来安全地传递消息。

语法/用法

postMessage 适合以下情况:

  • 通过 window.open() 打开的新标签页与原页面通信
  • iframe 与父页面通信
  • 不同源页面之间进行消息通知

注意事项

  • postMessage 不是广播机制,它依赖你拿到目标窗口对象。
  • 安全性关键点不在“能不能发”,而在“发给谁”和“收消息时是否校验来源”。

三、postMessage 的基本使用方式 必须掌握

3.1 发送消息

概念说明

调用 postMessage 的前提,是你先拿到目标窗口对象。例如:

  • 原页面通过 window.open() 拿到新标签页对象
  • 新页面通过 window.opener 拿到打开它的原页面对象

语法/用法

基本调用形式:

ts
targetWindow.postMessage(message, targetOrigin);

其中:

  • message:发送的数据,可以是字符串、对象等
  • targetOrigin:目标源,用于限制消息发送给哪个源

代码示例

ts
const newPage = window.open("http://tomok.cn:5173/login");

newPage?.postMessage("hello from old tab", "http://tomok.cn:5173");

注意事项

  • 课程原文里使用 * 作为目标源是为了方便测试,但生产环境不建议默认这么写。
  • 更推荐显式指定 targetOrigin,例如 http://localhost:5173

3.2 接收消息

概念说明

接收方需要监听浏览器的 message 事件。事件对象里最关键的是:

  • event.data:消息内容
  • event.origin:发送方源
  • event.source:发送消息的窗口对象

代码示例

ts
window.addEventListener("message", (event) => {
  if (event.origin !== "http://localhost:5173") {
    return;
  }

  console.log("收到消息:", event.data);
});

注意事项

  • 原文中的 orange 实际应为 origin
  • 接收消息时一定要校验 event.origin,不要无条件信任任意来源。

四、新页面发消息给旧页面 必须掌握

4.1 window.opener 的作用

概念说明

当一个页面是由另一个页面通过 window.open() 打开的,新页面中通常可以通过 window.opener 拿到原页面窗口对象。

这就意味着:

  • 原页面可以打开新页面
  • 新页面也可以反向给原页面发消息

代码示例

ts
console.log(window.opener);

if (window.opener) {
  window.opener.postMessage(
    "hello from new tab",
    "http://localhost:5173"
  );
}

注意事项

  • 如果页面不是通过 window.open() 打开的,window.opener 通常为 null
  • 如果用户直接访问登录页,而不是从原页面打开,就不能依赖 window.opener

4.2 实现链路

语法/用法

从新页面向旧页面发送消息的完整流程:

  1. 原页面打开新标签页
  2. 新页面通过 window.opener 获取原页面引用
  3. 新页面调用 window.opener.postMessage(...)
  4. 原页面监听 message 事件并校验 origin
  5. 原页面处理消息

注意事项

  • 如果你发现消息收到两次,先检查是否打开了多个标签页,或者是否重复注册了监听器。

五、旧页面发消息给新页面 必须掌握

5.1 window.open() 的返回值

概念说明

原页面调用 window.open() 后,会得到一个新标签页的窗口对象。只要这个对象还有效,原页面就可以给新页面发消息。

代码示例

ts
const newPage = window.open("http://tomok.cn:5173/login");

setTimeout(() => {
  if (newPage && typeof newPage.postMessage === "function") {
    newPage.postMessage(
      "hello from old tab",
      "http://tomok.cn:5173"
    );
  }
}, 5000);

注意事项

  • 课程原文里用 setTimeout 是因为新页面加载需要时间,这种做法适合演示。
  • 真实业务里,更推荐由新页面加载完成后主动回传“ready”消息,再由旧页面发送正式消息。

5.2 实现链路

语法/用法

从旧页面向新页面发送消息的流程:

  1. 原页面调用 window.open()
  2. 保存返回的新窗口对象
  3. 新页面加载并开始监听 message
  4. 原页面调用 newPage.postMessage(...)
  5. 新页面收到消息后处理

注意事项

  • window.open() 可能因为浏览器拦截弹窗而失败,要先判断返回值是否存在。
  • 如果目标页尚未完成监听注册,消息可能发过去但没人接收。

六、targetOriginevent.origin 的安全意义 必须掌握

6.1 为什么不能滥用 *

概念说明

postMessage 的第二个参数 targetOrigin 用来指定“消息允许发给哪个源”。如果写成 *,表示允许发给任意源。

代码示例

ts
// 仅适合临时调试
targetWindow.postMessage("test", "*");

// 更安全的方式
targetWindow.postMessage("test", "http://localhost:5173");

注意事项

  • 调试时可以用 * 简化操作。
  • 生产环境应尽量指定明确的源,避免消息被错误页面接收。

6.2 为什么接收端也必须校验

概念说明

仅仅在发送端指定 targetOrigin 还不够,接收端仍然必须校验 event.origin。因为接收端无法假设所有发来的消息都可信。

代码示例

ts
window.addEventListener("message", (event) => {
  const allowedOrigins = [
    "http://localhost:5173",
    "http://tomok.cn:5173",
  ];

  if (!allowedOrigins.includes(event.origin)) {
    return;
  }

  console.log("安全接收:", event.data);
});

注意事项

  • 发送端控制“发给谁”。
  • 接收端控制“收谁的消息”。
  • 两边都做限制,才是完整的安全处理。

七、课程实操中的几个关键现象 重要

7.1 直接访问页面时 window.openernull

概念说明

老师在课程里演示:如果你直接访问登录页,而不是从原页面通过 window.open() 打开,那么 window.opener 会是 null

注意事项

  • 这不是报错,而是正常行为。
  • 是否存在 window.opener,取决于页面的打开方式。

7.2 消息收到多次,不一定是 API 问题

概念说明

课程中提到“为什么接收到两条消息”,最终原因是之前开了多个标签页。

注意事项

  • 先排查是否重复打开了多个页面
  • 再排查是否重复绑定了 message 监听器
  • 最后再怀疑业务逻辑是否真的重复发送

7.3 页面里已有其他功能在接收消息

概念说明

原文里提到页面上的 Turnstile 一直在接收消息,影响了测试判断。这说明页面里可能已经存在其他基于消息机制的脚本。

注意事项

  • 调试 postMessage 时,要排除页面中已有 SDK、插件、验证码脚本的干扰。
  • 测试前可以先注释无关逻辑,避免把第三方脚本消息误判成自己的业务消息。

代码实战案例

需求描述

实现两个不同源的页面之间点对点通信:

  • 原页面打开新页面
  • 新页面向原页面发送消息
  • 原页面再向新页面回发消息
  • 双方都做 origin 校验

完整实现代码

原页面示例:

ts
const loginUrl = "http://tomok.cn:5173/login";
const loginOrigin = "http://tomok.cn:5173";

window.addEventListener("message", (event) => {
  if (event.origin !== loginOrigin) {
    return;
  }

  console.log("old tab received:", event.data);
});

const newPage = window.open(loginUrl);

setTimeout(() => {
  if (!newPage) {
    return;
  }

  newPage.postMessage("hello from old tab", loginOrigin);
}, 3000);

新页面示例:

ts
const dashboardOrigin = "http://localhost:5173";

window.addEventListener("message", (event) => {
  if (event.origin !== dashboardOrigin) {
    return;
  }

  console.log("new tab received:", event.data);
});

if (window.opener) {
  window.opener.postMessage("hello from new tab", dashboardOrigin);
}

代码逐行解析

  1. 原页面先定义目标页面地址与目标源。
  2. 原页面监听 message 事件,用于接收新页面回传的消息。
  3. 原页面通过 window.open() 打开新标签页。
  4. 原页面等待一段时间后,使用 newPage.postMessage() 给新页面发消息。
  5. 新页面加载完成后,也监听自己的 message 事件。
  6. 新页面通过 window.opener 获取原页面窗口对象。
  7. 如果 window.opener 存在,新页面就调用 postMessage() 回传消息。
  8. 双方都通过 origin 做来源校验,避免误收不可信消息。

常见问题与解决方案

问题原因分析解决方案
为什么 BroadcastChannel 在这里不能替代 postMessage当前场景不是同源页面,BroadcastChannel 主要用于同源通信跨源标签页通信优先考虑 postMessage
为什么新页面里 window.openernull页面不是通过 window.open() 打开的,而是用户直接访问只在“被原页面打开”的场景下使用 window.opener
为什么消息收不到?targetOrigin 配错、监听器未注册完成、页面尚未加载完成检查目标源、监听时机和新页面是否真正建立成功
为什么消息会收到两次?可能开了多个标签页,或重复绑定了 message 监听器关闭多余页面,确认监听器只注册一次
为什么把 targetOrigin 改成另一个端口后就收不到?目标源不匹配,浏览器不会把消息发给错误的源保证 targetOrigin 与目标页实际源完全一致
为什么不能无脑使用 *会降低消息发送范围的限制,带来安全风险生产环境显式指定允许的目标源,并在接收端校验 event.origin

学习要点总结

  1. localStorageBroadcastChannel 更偏同源通信,而 postMessage 更适合跨源场景。
  2. 新页面给旧页面发消息依赖 window.opener,旧页面给新页面发消息依赖 window.open() 的返回值。
  3. targetOrigin 决定消息发给谁,event.origin 决定消息收不收。
  4. * 适合临时测试,不适合作为默认生产写法。
  5. 调试 postMessage 时,要先排除重复标签页、重复监听器和第三方脚本干扰。

术语纠正与内容优化

  • 原文中的 taps,结合上下文应为 tabs
  • 原文中的 寓意,应为 ,更准确的说法是“同源”。
  • 原文中的 orange,规范属性名应为 origin
  • 原文中的 post因素,应理解为 hosts 映射后的测试域名,而不是独立技术名词。
  • 原文中的 on monday,结合上下文应为 Vue 的 onMounted
  • 原文中的 council LOG,应为 console.log
  • 原文中的 原测,结合上下文应理解为“原页面”或“旧标签页”。
  • 原文里的 hello from old chap,更自然的表达应为 hello from old tab

延伸学习资源

参考说明

本文结合课程原文整理,并对其中的口语化表述、测试场景和代码结构做了规范化处理。重点保留了“新标签页与旧标签页双向通信”“origin 校验”“指定目标源与使用 * 的差异”等知识点,代码示例在不改变课程核心思路的前提下做了更适合复习和工程实践的整理。