| 项目 | 内容 |
|---|---|
| 课程名称 | 未提供 |
| 当前章节 | 浏览器标签页之间的 postMessage 通信扩展 |
| 知识领域 | 浏览器通信机制 / 前端工程化 / 多标签页协作 |
| 学习目标 | 理解同源与跨源标签页通信的差异,掌握 postMessage 的发送、接收、目标源控制与安全校验 |
| 整理时间 | 2026-03-29 |
| 资料校对 | 依据浏览器标准能力对 postMessage、BroadcastChannel、window.opener 的概念边界做了规范化整理 |
一、为什么需要标签页之间通信 必须掌握
1.1 多标签页通信的常见场景
概念说明
很多后台系统、工作台系统或管理端应用,会同时打开多个标签页。例如:
- 主页面打开一个新的登录页或详情页
- 新页面完成操作后通知旧页面刷新状态
- 多个标签页之间同步登录态、刷新令牌或业务配置
- 从一个操作页打开另一个业务页,并在处理完成后回传结果
课程中提到的“管理后台应用做一个简单的 tabs”,本质上就是在讨论多个页面实例之间如何交换消息。
语法/用法
常见通信方案包括:
localStorage + storage事件BroadcastChannelwindow.postMessage
注意事项
- 不同方案的适用前提不一样,最关键的边界条件就是“是否同源”。
- 不是所有浏览器页面之间通信都能直接互发消息,能否通信取决于 API 约束和窗口引用关系。
二、同源方案与跨源方案的边界 必须掌握
2.1 什么是同源
概念说明
浏览器里的“同源”指的是协议、域名、端口三者都一致。例如:
http://localhost:5173和http://localhost:5173:同源http://localhost:5173和http://localhost:5174:不同源http://localhost:5173和http://toimok.cn:5173:不同源
注意事项
- 课程里通过本地
hosts映射把一个自定义域名指向本机服务,本质上仍然属于“不同域名”的测试场景。 - 即使两个页面都运行在同一台电脑上,只要源不同,浏览器安全边界依然成立。
2.2 不同方案的适用范围
概念说明
课程里提到 localhost 和 BroadcastChannel 都是在同源情况下使用,而如果“不在一个域上面”,就需要通过 postMessage 来通信。
这条判断基本是对的,但更准确的说法是:
BroadcastChannel:要求同源localStorage + storage:要求同源postMessage:可用于跨源通信,但前提是你能拿到目标窗口引用
代码示例
const channel = new BroadcastChannel("demo-channel");
channel.postMessage({ type: "refresh" });注意事项
postMessage并不是“自动发现所有页面”的广播机制,它更像是“拿着某个窗口引用,向它定点发消息”。- 如果两个页面互相没有窗口引用关系,
postMessage也无法凭空建立通信。
三、postMessage 的核心工作原理 必须掌握
3.1 谁发,谁收
概念说明
postMessage 的基础模型是:
- 发送方:持有目标窗口对象
- 接收方:通过
window.addEventListener("message", ...)监听消息
课程中的两种情况分别是:
- 新开的页面给旧页面发消息
- 旧页面给新开的页面发消息
语法/用法
发送消息的基本形式:
targetWindow.postMessage(message, targetOrigin);接收消息的基本形式:
window.addEventListener("message", (event) => {
console.log(event.data);
});注意事项
message可以是字符串、对象、数组等可结构化克隆的数据。targetOrigin不应该默认用*,除非你明确知道这是测试场景。
3.2 event.origin 与 targetOrigin
概念说明
这是 postMessage 最重要的安全点:
targetOrigin:发送时指定“允许谁接收”event.origin:接收时判断“消息究竟来自谁”
课程中老师先用 * 测试,再切换成具体源地址,这正是从“宽松测试”走向“安全限定”的过程。
代码示例
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:5173") {
return;
}
console.log("收到可信来源消息", event.data);
});注意事项
- 接收端一定要校验
event.origin,否则任何来源都可能向你发消息。 - 发送端最好指定精确的
targetOrigin,例如http://localhost:5173,而不是直接写*。
四、新页面给旧页面发消息 必须掌握
4.1 window.opener 是什么
概念说明
如果一个页面是通过另一个页面 window.open() 打开的,那么新页面里通常可以通过 window.opener 拿到“打开它的旧页面窗口对象”。
课程里老师在新开的登录页中打印 window.opener,发现:
- 直接单独访问登录页时,
window.opener是null - 从旧页面通过
window.open()打开时,window.opener才存在
代码示例
console.log(window.opener);注意事项
- 如果页面是用户手动输入地址打开的,不存在
opener。 - 如果打开窗口时使用了
noopener,那么window.opener也会是null。
4.2 从新页面发消息到旧页面
语法/用法
流程如下:
- 旧页面调用
window.open()打开新页面 - 新页面通过
window.opener拿到旧页面引用 - 新页面调用
window.opener.postMessage(...) - 旧页面监听
message事件并处理数据
代码示例
旧页面:
window.addEventListener("message", (event) => {
if (event.origin !== "http://toimok.cn:5173") {
return;
}
console.log("旧页面收到消息:", event.data);
});新页面:
if (window.opener) {
window.opener.postMessage(
"hello from new tab",
"http://localhost:5173"
);
}注意事项
- 新页面发送给旧页面时,
targetOrigin应写旧页面自己的源。 - 如果你写成错误的端口或域名,例如
5174,旧页面就收不到消息。
五、旧页面给新页面发消息 必须掌握
5.1 为什么需要保存 window.open() 的返回值
概念说明
如果要从旧页面向新页面发送消息,旧页面必须先拿到新页面的窗口引用。这个引用通常来自 window.open() 的返回值。
代码示例
const newPage = window.open("http://toimok.cn:5173/login", "_blank");注意事项
- 没有这个窗口引用,就无法直接调用
postMessage。 - 如果浏览器阻止弹窗,
newPage可能是null。
5.2 从旧页面发消息到新页面
语法/用法
流程如下:
- 旧页面调用
window.open()打开新页面,并保存返回值 - 新页面自己监听
message - 旧页面在合适时机调用
newPage.postMessage(...) - 新页面在事件回调里处理消息
代码示例
旧页面:
const newPage = window.open("http://toimok.cn:5173/login", "_blank");
setTimeout(() => {
if (newPage && typeof newPage.postMessage === "function") {
newPage.postMessage(
"hello from old tab",
"http://toimok.cn:5173"
);
}
}, 5000);新页面:
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:5173") {
return;
}
console.log("新页面收到消息:", event.data);
});注意事项
- 课程中用
setTimeout是为了等新页面加载完成,这种做法适合演示,但不够稳妥。 - 更工程化的做法是新页面加载完成后主动发送一个“ready”消息,再由旧页面开始通信。
六、完整的双向通信模型 重要
6.1 点对点通信的两种方向
概念说明
课程最后总结的“点对点通信”实际上有两个方向:
- 新页面 -> 旧页面:依赖
window.opener - 旧页面 -> 新页面:依赖
window.open()返回值
代码示例
// 旧页面打开新页面
const child = window.open("http://toimok.cn:5173/login", "_blank");
// 新页面发回旧页面
window.opener?.postMessage("hello from child", "http://localhost:5173");
// 旧页面发给新页面
child?.postMessage("hello from parent", "http://toimok.cn:5173");注意事项
- 双向通信不等于任意页面都能互相通信,它仍然依赖这两个页面之间存在“打开者”和“被打开者”的引用关系。
七、安全与工程实践建议 必须掌握
7.1 不要滥用 *
概念说明
课程中老师先用 * 进行测试,这在演示阶段没问题,但在真实项目中应尽量避免。
注意事项
*表示接受任意目标源,适合快速验证,不适合生产默认配置。- 生产环境应使用明确的源地址,例如
https://admin.example.com。
7.2 接收端必须校验来源
代码示例
window.addEventListener("message", (event) => {
const allowList = [
"http://localhost:5173",
"http://toimok.cn:5173",
];
if (!allowList.includes(event.origin)) {
return;
}
console.log(event.data);
});注意事项
- 除了校验
origin,还可以进一步校验event.data.type、数据结构和必填字段。 - 不要直接把收到的数据当成可信命令执行。
7.3 推荐使用结构化消息
概念说明
相比直接发送一段字符串,发送结构化对象更利于扩展。
代码示例
newPage?.postMessage(
{
type: "LOGIN_SUCCESS",
payload: {
userId: "1001",
},
},
"http://toimok.cn:5173"
);注意事项
- 推荐统一消息格式,例如
type + payload + requestId。 - 多业务共用通信通道时,消息协议最好先约定好。
八、与课程原文对应的 Vue 场景整理 重要
8.1 在 Vue 组件中监听消息
概念说明
课程里提到在 login.vue、index.vue 这样的页面组件中做监听。对于 Vue 项目,推荐在组件挂载时注册监听,在组件卸载时移除监听。
代码示例
import { onMounted, onUnmounted } from "vue";
function handleMessage(event: MessageEvent) {
if (event.origin !== "http://localhost:5173") {
return;
}
console.log(event.data);
}
onMounted(() => {
window.addEventListener("message", handleMessage);
});
onUnmounted(() => {
window.removeEventListener("message", handleMessage);
});注意事项
- 不要在组件每次渲染时重复注册监听。
- 页面切换后如果不移除监听,容易出现重复接收消息的问题。
代码实战案例
需求描述
实现两个不同源标签页之间的点对点通信:
- 旧页面打开新页面
- 新页面加载后给旧页面发送一条消息
- 旧页面在确认新页面已打开后,再向新页面发送一条消息
- 双方都做来源校验
完整实现代码
旧页面示例:
const childPage = window.open("http://toimok.cn:5173/login", "_blank");
window.addEventListener("message", (event) => {
if (event.origin !== "http://toimok.cn:5173") {
return;
}
console.log("旧页面收到:", event.data);
if (event.data?.type === "CHILD_READY") {
childPage?.postMessage(
{
type: "PARENT_HELLO",
payload: "hello from old tab",
},
"http://toimok.cn:5173"
);
}
});新页面示例:
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:5173") {
return;
}
console.log("新页面收到:", event.data);
});
if (window.opener) {
window.opener.postMessage(
{
type: "CHILD_READY",
payload: "hello from new tab",
},
"http://localhost:5173"
);
}代码逐行解析
- 旧页面先用
window.open()打开新页面,并保存返回的窗口引用。 - 旧页面注册
message监听,只接受来自http://toimok.cn:5173的消息。 - 新页面注册自己的
message监听,只接受来自http://localhost:5173的消息。 - 新页面如果检测到
window.opener存在,说明它是被旧页面打开的,可以向旧页面发回“我已就绪”的消息。 - 旧页面收到
CHILD_READY后,再向新页面发送第二条消息。 - 通过这种方式,就不必依赖固定的
setTimeout猜测页面何时加载完成。
常见问题与解决方案
| 问题 | 原因分析 | 解决方案 |
|---|---|---|
为什么 BroadcastChannel 可以用,但换了域名后就不行了? | BroadcastChannel 仅支持同源上下文 | 跨源场景改用 postMessage,并确保拿得到目标窗口引用 |
为什么新页面里 window.opener 是 null? | 页面不是通过 window.open() 打开的,或使用了 noopener | 通过旧页面显式打开新页面,并避免切断 opener |
为什么用了 postMessage("*") 还是收不到? | 发送目标窗口引用不对,或接收端没有监听消息 | 先确认 window.open() 返回值、window.opener 和监听逻辑是否正常 |
为什么把目标源从 5173 改成 5174 后就收不到消息? | targetOrigin 必须与接收页面源严格匹配 | 使用正确的协议、域名和端口 |
| 为什么会收到两条消息? | 可能注册了多次监听,或者同时打开了多个标签页实例 | 清理旧监听、关闭多余标签页,并在 Vue 组件卸载时移除监听 |
| 为什么旧页面发消息时要延迟几秒? | 新页面可能还没加载完,过早发送时接收端还没准备好 | 使用“ready 消息握手”替代纯 setTimeout 等待 |
学习要点总结
BroadcastChannel和storage事件更适合同源多标签页通信,跨源场景通常要用postMessage。postMessage的前提不是“页面都开着就行”,而是必须持有目标窗口引用。- 新页面发回旧页面依赖
window.opener,旧页面发给新页面依赖window.open()返回值。 targetOrigin和event.origin是postMessage安全设计的核心,生产环境不要滥用*。- 比起直接延迟发送消息,更稳妥的做法是设计一套“ready 握手”协议。
术语纠正与内容优化
- 原文中的
taps,结合上下文应为tabs。 - 原文中的
寓意上面、一个寓意,结合上下文应为“同一个域”或“同源”。 - 原文中的
orange,应为origin。 - 原文中的
post message,规范写法应为postMessage。 - 原文中的
on monday,结合 Vue 场景应为onMounted。 - 原文中的
logging.vue,结合上下文更可能是login.vue。 - 原文中的
ten style仍然是上一节验证码逻辑残留,不属于本节postMessage的核心知识点,整理时已剔除干扰信息。 - 原文中用
setTimeout等待新页面加载完成,这适合演示;工程上更推荐让新页面主动发送“ready”消息再开始正式通信。
延伸学习资源
- MDN
window.postMessage文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage - MDN
Window.opener文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/opener - MDN
BroadcastChannel文档:https://developer.mozilla.org/zh-CN/docs/Web/API/BroadcastChannel - Vue 3 生命周期文档:https://cn.vuejs.org/guide/essentials/lifecycle.html
- 学习建议:自己分别实现“同源 BroadcastChannel 通信”和“跨源 postMessage 通信”,比较两者使用边界。
- 练习建议:把字符串消息升级成统一的对象协议,例如
type + payload + requestId,并增加来源白名单校验。
参考说明
本文结合课程原文整理,并对其中的口语化表述、API 命名和安全边界做了规范化修正。postMessage、event.origin、targetOrigin、window.opener、window.open() 返回值、BroadcastChannel 的适用范围等信息,依据浏览器标准能力进行了工程化整理;代码示例在保留课程原始思路的基础上,补充了更稳妥的“ready 握手”通信模式。