| 项目 | 内容 |
|---|---|
| 课程名称 | 未提供 |
| 当前章节 | 浏览器标签页之间的消息通信扩展 |
| 知识领域 | 浏览器 API / 前端通信 / 多标签页协作 |
| 学习目标 | 理解 postMessage 在跨标签页场景中的使用方式,掌握消息发送、接收、来源校验和双向通信的实现思路 |
| 整理时间 | 2026-03-29 |
| 资料说明 | 基于课程原文整理,并对术语、代码写法和安全注意事项做规范化处理 |
一、为什么需要跨标签页通信 必须掌握
1.1 常见业务场景
概念说明
在后台管理系统、统一登录、支付跳转、第三方授权、单点登录等场景中,页面之间经常需要交换状态或通知结果。
例如:
- 在一个标签页中打开登录页,登录成功后通知原页面刷新状态
- 在管理后台中打开新标签页,完成某个操作后同步回原页面
- 在一个域名打开的中转页,将消息发回原始业务页
语法/用法
标签页之间通信,常见方案包括:
localStorage+storage事件BroadcastChannelwindow.postMessage
注意事项
localStorage事件和BroadcastChannel更适合同源页面之间通信。- 如果两个页面不在同一个源上,
postMessage往往是更合适的方案。
二、同源通信方案与跨源通信方案的区别 重要
2.1 什么叫同源
概念说明
浏览器中的“同源”指的是协议、域名、端口三者都相同。
例如:
http://localhost:5173和http://localhost:5173是同源http://localhost:5173和http://localhost:5174不是同源http://localhost:5173和http://tomok.cn:5173不是同源
注意事项
- 原文里提到的
localhost和自定义域名映射,即使都指向本机服务,只要域名不同,也不是同源。 - “在同一个浏览器里打开”不等于“同源”。
2.2 什么时候选 postMessage
概念说明
如果页面之间不是同源关系,像 BroadcastChannel 这类方案就不能直接跨源使用。这时可以利用浏览器提供的 window.postMessage 来安全地传递消息。
语法/用法
postMessage 适合以下情况:
- 通过
window.open()打开的新标签页与原页面通信 iframe与父页面通信- 不同源页面之间进行消息通知
注意事项
postMessage不是广播机制,它依赖你拿到目标窗口对象。- 安全性关键点不在“能不能发”,而在“发给谁”和“收消息时是否校验来源”。
三、postMessage 的基本使用方式 必须掌握
3.1 发送消息
概念说明
调用 postMessage 的前提,是你先拿到目标窗口对象。例如:
- 原页面通过
window.open()拿到新标签页对象 - 新页面通过
window.opener拿到打开它的原页面对象
语法/用法
基本调用形式:
targetWindow.postMessage(message, targetOrigin);其中:
message:发送的数据,可以是字符串、对象等targetOrigin:目标源,用于限制消息发送给哪个源
代码示例
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:发送消息的窗口对象
代码示例
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 拿到原页面窗口对象。
这就意味着:
- 原页面可以打开新页面
- 新页面也可以反向给原页面发消息
代码示例
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 实现链路
语法/用法
从新页面向旧页面发送消息的完整流程:
- 原页面打开新标签页
- 新页面通过
window.opener获取原页面引用 - 新页面调用
window.opener.postMessage(...) - 原页面监听
message事件并校验origin - 原页面处理消息
注意事项
- 如果你发现消息收到两次,先检查是否打开了多个标签页,或者是否重复注册了监听器。
五、旧页面发消息给新页面 必须掌握
5.1 window.open() 的返回值
概念说明
原页面调用 window.open() 后,会得到一个新标签页的窗口对象。只要这个对象还有效,原页面就可以给新页面发消息。
代码示例
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 实现链路
语法/用法
从旧页面向新页面发送消息的流程:
- 原页面调用
window.open() - 保存返回的新窗口对象
- 新页面加载并开始监听
message - 原页面调用
newPage.postMessage(...) - 新页面收到消息后处理
注意事项
window.open()可能因为浏览器拦截弹窗而失败,要先判断返回值是否存在。- 如果目标页尚未完成监听注册,消息可能发过去但没人接收。
六、targetOrigin 与 event.origin 的安全意义 必须掌握
6.1 为什么不能滥用 *
概念说明
postMessage 的第二个参数 targetOrigin 用来指定“消息允许发给哪个源”。如果写成 *,表示允许发给任意源。
代码示例
// 仅适合临时调试
targetWindow.postMessage("test", "*");
// 更安全的方式
targetWindow.postMessage("test", "http://localhost:5173");注意事项
- 调试时可以用
*简化操作。 - 生产环境应尽量指定明确的源,避免消息被错误页面接收。
6.2 为什么接收端也必须校验
概念说明
仅仅在发送端指定 targetOrigin 还不够,接收端仍然必须校验 event.origin。因为接收端无法假设所有发来的消息都可信。
代码示例
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.opener 为 null
概念说明
老师在课程里演示:如果你直接访问登录页,而不是从原页面通过 window.open() 打开,那么 window.opener 会是 null。
注意事项
- 这不是报错,而是正常行为。
- 是否存在
window.opener,取决于页面的打开方式。
7.2 消息收到多次,不一定是 API 问题
概念说明
课程中提到“为什么接收到两条消息”,最终原因是之前开了多个标签页。
注意事项
- 先排查是否重复打开了多个页面
- 再排查是否重复绑定了
message监听器 - 最后再怀疑业务逻辑是否真的重复发送
7.3 页面里已有其他功能在接收消息
概念说明
原文里提到页面上的 Turnstile 一直在接收消息,影响了测试判断。这说明页面里可能已经存在其他基于消息机制的脚本。
注意事项
- 调试
postMessage时,要排除页面中已有 SDK、插件、验证码脚本的干扰。 - 测试前可以先注释无关逻辑,避免把第三方脚本消息误判成自己的业务消息。
代码实战案例
需求描述
实现两个不同源的页面之间点对点通信:
- 原页面打开新页面
- 新页面向原页面发送消息
- 原页面再向新页面回发消息
- 双方都做
origin校验
完整实现代码
原页面示例:
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);新页面示例:
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);
}代码逐行解析
- 原页面先定义目标页面地址与目标源。
- 原页面监听
message事件,用于接收新页面回传的消息。 - 原页面通过
window.open()打开新标签页。 - 原页面等待一段时间后,使用
newPage.postMessage()给新页面发消息。 - 新页面加载完成后,也监听自己的
message事件。 - 新页面通过
window.opener获取原页面窗口对象。 - 如果
window.opener存在,新页面就调用postMessage()回传消息。 - 双方都通过
origin做来源校验,避免误收不可信消息。
常见问题与解决方案
| 问题 | 原因分析 | 解决方案 |
|---|---|---|
为什么 BroadcastChannel 在这里不能替代 postMessage? | 当前场景不是同源页面,BroadcastChannel 主要用于同源通信 | 跨源标签页通信优先考虑 postMessage |
为什么新页面里 window.opener 是 null? | 页面不是通过 window.open() 打开的,而是用户直接访问 | 只在“被原页面打开”的场景下使用 window.opener |
| 为什么消息收不到? | targetOrigin 配错、监听器未注册完成、页面尚未加载完成 | 检查目标源、监听时机和新页面是否真正建立成功 |
| 为什么消息会收到两次? | 可能开了多个标签页,或重复绑定了 message 监听器 | 关闭多余页面,确认监听器只注册一次 |
为什么把 targetOrigin 改成另一个端口后就收不到? | 目标源不匹配,浏览器不会把消息发给错误的源 | 保证 targetOrigin 与目标页实际源完全一致 |
为什么不能无脑使用 *? | 会降低消息发送范围的限制,带来安全风险 | 生产环境显式指定允许的目标源,并在接收端校验 event.origin |
学习要点总结
localStorage、BroadcastChannel更偏同源通信,而postMessage更适合跨源场景。- 新页面给旧页面发消息依赖
window.opener,旧页面给新页面发消息依赖window.open()的返回值。 targetOrigin决定消息发给谁,event.origin决定消息收不收。*适合临时测试,不适合作为默认生产写法。- 调试
postMessage时,要先排除重复标签页、重复监听器和第三方脚本干扰。
术语纠正与内容优化
- 原文中的
taps,结合上下文应为tabs。 - 原文中的
寓意,应为域或源,更准确的说法是“同源”。 - 原文中的
orange,规范属性名应为origin。 - 原文中的
post因素,应理解为hosts映射后的测试域名,而不是独立技术名词。 - 原文中的
on monday,结合上下文应为 Vue 的onMounted。 - 原文中的
council LOG,应为console.log。 - 原文中的
原测,结合上下文应理解为“原页面”或“旧标签页”。 - 原文里的
hello from old chap,更自然的表达应为hello from old tab。
延伸学习资源
- MDN
Window.postMessage()文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage - MDN
message事件文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/message_event - MDN
BroadcastChannel文档:https://developer.mozilla.org/zh-CN/docs/Web/API/BroadcastChannel - 学习建议:分别实现“新页面通知旧页面登录成功”和“旧页面向新页面发送初始化参数”两个小案例。
- 练习建议:给消息体补充
type字段,例如{ type: "LOGIN_SUCCESS", payload: ... },避免后续多种消息混淆。
参考说明
本文结合课程原文整理,并对其中的口语化表述、测试场景和代码结构做了规范化处理。重点保留了“新标签页与旧标签页双向通信”“origin 校验”“指定目标源与使用 * 的差异”等知识点,代码示例在不改变课程核心思路的前提下做了更适合复习和工程实践的整理。