入门:跨上下文场景与四机制定位
基于 WHATWG HTML(跨文档消息 / 通道消息 / 广播频道)现行标准与各浏览器 Baseline 状态 · 核于 2026-07
速查
- 一句话定位:浏览器里主文档、
<iframe>、window.open弹窗、Web Worker、同源多标签页各有独立全局与事件循环,不共享变量,只能靠「传消息」通信;本叶讲这组标准 API。 - 四类场景:① 主页 ↔
<iframe>(嵌入第三方 / 被第三方嵌入)② 主页 ↔window.open弹窗(window.opener反向)③ 主页 ↔ Worker ④ 同源多个标签页 / 窗口互相同步。 - 四种机制:
window.postMessage(跨文档 / 跨源点对点)·MessageChannel/MessagePort(专属双向管道 + 端口转移)·BroadcastChannel(同源一对多广播)· Web Locks(navigator.locks,不是传消息而是协调 / 选主)。 postMessage一句话:目标window.postMessage(数据, targetOrigin, [transfer]),接收端window.addEventListener("message", ...);跨源可用,是<iframe>/ 弹窗通信基石。- 两条安全铁律(详见 postMessage 页):发送端
targetOrigin别用*传敏感数据;接收端 必须先校验event.origin再处理,还要校验消息结构(防 XSS)。 event.source:指向发消息的那个window,用来安全回复(event.source.postMessage(...)),不是身份凭证——身份看origin。MessageChannel:new MessageChannel()得port1/port2一对;一端留用、另一端用 transfer 交给<iframe>/ Worker,建立一条私有直连管道。port.start()坑:用addEventListener("message")收消息必须显式port.start();只有port.onmessage =赋值才隐式启动(详见 MessageChannel 页)。BroadcastChannel:new BroadcastChannel("名字")→bc.postMessage(x)广播给同源所有连到同名频道的上下文;不回发给自己、消息不带发送者标识。- Web Locks 选主:
navigator.locks.request("名", async () => { /* 持锁期间不 resolve 就一直持有 */ })——多标签页里只让一个拿到锁 = leader,做同步 / 轮询的唯一执行者。 - 能传什么:
postMessage/BroadcastChannel走结构化克隆(对象 /Date/Map/ArrayBuffer/Blob可,函数 / DOM 节点不可);大ArrayBuffer/MessagePort/ImageBitmap可用 transfer 零拷贝。 - 选型速记:跨源 / 跨文档点对点 →
postMessage;要私有管道 / 交端口给 Worker →MessageChannel;同源多标签广播 →BroadcastChannel;多标签「只让一个干活」→ Web Locks。 - Baseline:
postMessage与通道消息自 2015-09 起 Baseline;BroadcastChannel与 Web Locks 自 2022-03 起 Baseline Widely available(Web Locks 仅安全上下文 HTTPS)。 - 相邻叶只对比不展开:storage 事件 → Web Storage 叶;
SharedWorker→ Web Workers 叶;Service Worker 转发 → Service Worker 叶。 - 进阶顺序:本页 → postMessage → MessageChannel → BroadcastChannel → 多标签页方案与选主 → 参考。
一、本叶与相邻内容的分工
「跨上下文通信」在本站分本叶自身 + 三条相邻路线,各管一段:
| 问题 | 去哪读 |
|---|---|
| 跨源 / 跨文档点对点、私有管道、同源广播、多标签选主 | 本叶 |
| 同步 KV + 跨文档 storage 事件(老式多标签同步) | Web Storage 叶:API 与事件 |
多页共享单实例后台线程(SharedWorker) | Web Workers 叶:SharedWorker |
| 网络代理型 worker、可跨页转发(Service Worker) | Service Worker 与 PWA 叶 |
本叶聚焦四件标准 API:window.postMessage、MessageChannel / MessagePort、BroadcastChannel、Web Locks。storage 事件、SharedWorker、Service Worker 只在多标签页方案页作为同步方案对比点到并链接,不重复它们的完整 API。
二、什么是「跨上下文」:一页之内的多个执行环境
一个网页运行时往往不止一个 JavaScript 执行环境。它们各有独立的全局对象和事件循环,内存互不相通——一个环境里的变量另一个环境读不到,唯一的通信手段就是「传消息」:
- 主文档窗口:页面本体。
<iframe>子框架:嵌入的另一份文档,可能同源也可能跨源(第三方支付、登录、地图、富文本编辑器都是跨源<iframe>)。window.open弹窗 / 新窗口:被打开的窗口有window.opener指回打开它的窗口。- Web Worker:后台线程,无 DOM,与主线程只能靠
postMessage(这条线的编程细节在 Web Workers 叶,本叶讲的是通信机制本身)。 - 同源的多个标签页 / 窗口:用户开了好几个你的站点页面,它们之间要同步登录态、主题、数据。
同源策略(same-origin policy)把「跨源直接读对方变量 / DOM」这条路彻底堵死;哪怕同源,跨标签页也没有共享内存。所以浏览器提供了一组受控的消息通道,让这些环境在明确知道对方是谁的前提下安全交换数据。
三、四种机制,各占一个生态位
| 机制 | 拓扑 | 跨源? | 典型场景 | Baseline |
|---|---|---|---|---|
window.postMessage | 点对点(窗口 ↔ 窗口 / <iframe> / 弹窗 / Worker) | 可跨源 | 与第三方 <iframe>、window.open 弹窗通信 | 2015 起 |
MessageChannel / MessagePort | 点对点专属管道(可把一端转移出去) | 随宿主(转移到跨源 <iframe> 也可) | 建私有双向通道、Worker 直连、RPC 底座 | 2015-09 起 |
BroadcastChannel | 一对多广播 | 仅同源(同存储分区) | 多标签页同步:登出、主题、数据失效 | 2022-03 起 |
Web Locks(navigator.locks) | 协调 / 选主(不传数据) | 仅同源 | 多标签页选主(只让一个跑同步 / 轮询) | 2022-03 起(仅 HTTPS) |
一句话记住它们的分工:
- 要跨源、点对点——只有
postMessage能跨过同源策略,<iframe>与弹窗通信必用它。 - 要一条私有双向管道(不想所有消息都挤在
window.onmessage,或想把通信端交给 Worker / 子框架自己收发)——用MessageChannel,它本质是「两个用管道连起来的MessagePort」。 - 要一处改、同源处处变——用
BroadcastChannel,一句postMessage广播给所有同源标签页 / Worker。 - 要「多个标签页里只让一个干活」——用 Web Locks 做选主(leader election),这不是传消息,是协调。
四、第一个闭环:主页与 <iframe> 用 postMessage 通信
一个可直接运行的最小例子——父页面向 <iframe> 发消息,<iframe> 校验来源后回复:
html
<!-- 父页面 parent.html(源 https://parent.example) -->
<iframe id="child" src="https://child.example/child.html"></iframe>
<script>
const iframe = document.getElementById("child");
// 等子框架加载完再发,否则对方还没挂监听
iframe.addEventListener("load", () => {
// 第二参数是 targetOrigin:明确写子框架的源,绝不用 "*"
iframe.contentWindow.postMessage(
{ type: "greet", text: "你好,子框架" },
"https://child.example",
);
});
// 接收子框架的回复
window.addEventListener("message", (event) => {
// 头号安全点:先校验来源,非白名单直接丢弃
if (event.origin !== "https://child.example") return;
console.log("父页面收到回复:", event.data);
});
</script>html
<!-- 子框架 child.html(源 https://child.example) -->
<script>
window.addEventListener("message", (event) => {
// 同样先校验来源:只信任已知父页面
if (event.origin !== "https://parent.example") return;
console.log("子框架收到:", event.data);
// event.source 指向父窗口,用它安全回复;event.origin 回填 targetOrigin
event.source.postMessage(
{ type: "greet-ack", text: "收到,子框架已就绪" },
event.origin,
);
});
</script>这段代码已经浓缩了跨上下文通信最核心的四件事,第一次接触就该记住:
- 发送:
目标window.postMessage(数据, targetOrigin)——目标窗口的引用(这里是iframe.contentWindow)+ 明确的targetOrigin。 - 接收:给自己的
window挂message监听,数据在event.data。 - 安全:发送端
targetOrigin写死对方的源(不用*);接收端先if (event.origin !== 期望源) return;再处理——这是整套 API 最重要的一行。 - 回复:用
event.source(对方window的引用)加event.origin(对方的源)回消息,天然打到正确的对端。
安全细节、window.opener 弹窗方向、结构化克隆与 transfer、XSS 风险清单,全部展开在 window.postMessage 页。
五、如何选:一张决策图
面对一个具体需求,按这个顺序问自己:
- 对端是跨源的
<iframe>/ 弹窗 / 跨源窗口吗? → 只能用postMessage(唯一能跨源的机制)。若要在其上建私有双向管道(避免消息混在同一个window.onmessage、或让 Worker / 子框架自己收发),再叠一层MessageChannel:用一次postMessage把port2转移过去,之后两端各用自己的 port 直连。 - 对端是 Web Worker 吗? → Worker 本身就用
postMessage(见 Web Workers 叶);要多路复用或结构化 RPC,用MessageChannel建独立通道。 - 要把一件事同步给同源的所有标签页 / 窗口吗?(登出、切主题、购物车变化、缓存失效) →
BroadcastChannel,一对多广播、不回发给自己、三行代码。 - 要在多个标签页里选出「唯一负责人」吗?(只让一个标签页连 WebSocket / 跑轮询 / 做后台同步) → Web Locks 选主:所有标签页抢同名锁,拿到的当 leader,用「永不 resolve 的回调」一直持有。
- 只是想兼容很老的浏览器做多标签同步? → 老式 storage 事件 兜底(见 Web Storage 叶),但 2022 年后
BroadcastChannel已 Baseline,新项目首选它。
下一页从最基础也最危险的一件讲起——window.postMessage,跨源通信与它的两条安全铁律。