6 种浏览器跨窗口通信方案 - 从实际案例出发

浏览器跨窗口通信,听上去挺有技术感的,但实现起来方案倒是挺多的。本文将从一个实际的业务开发场景,带你了解如何实现浏览器跨窗口通信。

业务场景

一个常规的业务列表页,页面中提供了一个新增功能,由于新增功能的表单项内容比较多,所以交互上使用新开一个窗口来完成。这时问题来了,在新增完成后,如何通知列表页面刷新列表数据,以便展示出刚才新增的那一条数据。

各位可以先自己在心中简单想想,如果让你实现这个功能,你会使用什么方案。

解决方案

window.opener

window.opener 代表的是打开当前窗口的那个窗口的引用,例如:在 window A 中打开了 window B,B.opener 返回 A。

有了这个引用关系,我们就可以实现跨窗口通信:

  1. 列表页设置刷新列表方法 window.refreshList = () => {}
  2. 列表页通过 window.open 或者 <a href="newUrl" target="_blank" rel="opener">新增</a> 打开新增功能页面。
  3. 用户完成新增表单填写并提交,通过调用 window.opener.refreshList 方法来刷新列表页面数据,并关闭当前页。

有人可能注意到了,在 a 标签中我们使用到了 rel="opener" 属性,为什么要设置这个属性呢?

rel 属性定义了所链接的资源与当前文档的关系,常见的属性值有:

  • opener: 打开的新页面 window.opener 会指向前一个页面的 window。
  • noopener: 和 opener 相对应, window.opener 为空。
  • noreferrer:打开新页面时请求头不会包含 Referer,比如你未设置 noreferrer 的情况下,从 antd 打开百度,百度页面请求头就有 Referer: https://ant.design/
  • nofollow: 主要用于 SEO,告诉搜索引擎忽略该链接。

主要关注 opener 和 noopener 属性,a 标签默认情况下 rel=noopener,这代表打开的新增页面的 window.opener 对象会为空,所以需要设置 rel=opener。

那么又有一个问题,为什么 a 标签默认是 rel=noopener, 因为 opener 存在安全漏洞,比如你以 opener 的方式打开了一个未知的新页面,这个新页面可以通过 window.opener.location.href = 'fake.com' 重定向你的页面。

而 window.open 默认情况下 rel=opener,故打开的新页面可以拿到 window.opener 对象,不过要是打开第三方未知网站,建议设置为 noopener, 比如 window.open('https://baidu.com', 'title', 'noopener,noreferrer')。

BroadcastChannel

BroadcastChannel API 顾名思义,为"广播频道",适用于在同一域名 下的多个窗口、标签页或 iframe 之间进行实时消息广播。它的使用也非常简单,我们也看下如何通过它来实现上面的业务场景。

列表页创建一个 BroadcastChannel 实例来监听消息:

js 复制代码
// 创建 BroadcastChannel 实例
const channel = new BroadcastChannel('myChannel');

// 监听广播通道的消息
channel.addEventListener('message', (event) => {
  console.log('接收:', event.data); // { action: 'refresh' }
})

新增功能页面同样创建一个 BroadcastChannel 实例,频道名称需要和列表页一致:

js 复制代码
// 创建 BroadcastChannel 实例
const channel = new BroadcastChannel('myChannel');
// 向广播通道发送消息
channel.postMessage({ action: 'refresh' });
// 关闭频道
channel.close()

可以看到 BroadcastChannel 的使用很简单,双方创建同名频道的 BroadcastChannel 实例,然后一方监听 message 事件,一方使用 postMessage 广播内容。

postMessage

对于 postMessage,我们最常用的方式应该就是当前页面和 iframe 的跨域消息通信了,其实它也能完成跨窗口通信,核心就是能拿到新窗口的 window 对象,这个在 window.opener 方案中我们就知道通过设置 rel="opener" 就可以办到。

列表页打开新窗口,并监听 message 事件:

html 复制代码
<a href="./add.html" target="_blank" rel="opener">新增</a>
<script>
  // 不同与 BroadcastChannel,这边是监听 window 上的 message 事件
  window.addEventListener("message", receiveMessage);
  function receiveMessage(event) {
    console.log('接收:', event.data); // { action: 'refresh' }
  }
</script>

新增功能页面使用 window.opener.postMessage 发送消息:

js 复制代码
window.opener?.postMessage({ action: 'refresh' }, '*');

至此我们已经完成了上面的业务需求,postMessage 的优势在于可以跨域。

MessageChannel

MessageChannel API 允许我们创建一个新的消息通道,并通过它的两个 MessagePort 实例属性发送数据,同时它也可以跨域通信。

不同于 BroadcastChannel 的广播,MessageChannel 只提供双向通信通道,不过它既可以像 postMessage 一样用于 iframe 通信,也可以用于 Web Worker 之间进行通信。

要用 MessageChannel 实现跨窗口通信,方式有点类似 postMessage, 打开新页面时需要设置 rel="opener"。

列表页初始化 MessageChannel 实例,并在 port1 上监听 message 事件:

js 复制代码
// 创建 MessageChannel 实例
window.messageChannel = new MessageChannel();
const port1 = window.messageChannel.port1;
// port1 监听 message 事件
port1.onmessage = function(event) {
  console.log('接收:', event.data); // { action: 'refresh' }
};

新增功能页面使用 window.opener.messageChannel 拿到列表的 MessageChannel 实例,并使用 port2 的 postMessage 方法往 port1 通道上发送消息:

js 复制代码
// 获取 MessageChannel 实例
const messageChannel = window.opener.messageChannel;
const port2 = messageChannel.port2;
// 往 port1 发送消息
port2.postMessage({ action: 'refresh' });

需要注意的是 MessagePort 对象如果使用 addEventListener 监听 message 事件,就需要调用下 port.start() 方法,使用 onmessage 则可以不需要。

storage 事件

当 localStorage 或 sessionStorage 被修改时,将触发 storage 事件,利用这个机制,我们也可以完成跨窗口通信。同时因为用的是 localStorage 或 sessionStorage 方式,所以页面必须是同一域名下。

值得注意的是,sessionStorage 并不能满足上面的业务需求,sessionStorage 要想触发 storage 事件,必须在同一窗口,也就是一般只在当前页和其加载的同域名 iframe 下使用。还有一点就是当前页的 setItem 不会触发当前页的 storage 事件,只会触发其他窗口的。

列表页监听 storage 事件,判断是否是对应 key 值发生变化:

js 复制代码
window.addEventListener("storage", () => {
  console.log('发生变化的值:', event.key); 
  if (event.key === 'refresh') {
     // 刷新列表
  }
});

新增功能页面使用 localStorage 的 setItem 来触发列表的 storage 事件:

js 复制代码
window.localStorage.setItem('fresh', Date.now())

SharedWorker

SharedWorker 是 Web Workers API 的一种扩展,它允许在多个浏览器上下文中(例如多个页面或多个 iframe )共享一个 Worker。ShareWorker 遵守同源策略,也就是必须在同一域名下使用 SharedWorker。

先写个 worker.js 脚本:

js 复制代码
const ports = [];

onconnect = function (e) {
  const port = e.ports[0];
  ports.push(port);
  port.onmessage = function (e) {
    console.log("worker接收到的消息:", e.data);
    ports.forEach((p) => {
      p.postMessage(e.data);
    });
  };
};

列表页创建 ShareWorker 实例,然后监听 message 事件:

js 复制代码
const sharedWorker = new SharedWorker('./worker.js', 'test');
const port = sharedWorker.port;
port.onmessage = function (event) {
  console.log('接收:', event.data); // { action: 'refresh' }
};

新增功能页面使用 postMessage 发送消息给 worker,worker 在发送给各个主线程:

js 复制代码
const sharedWorker = new SharedWorker('./worker.js', 'test');
const port = sharedWorker.port;
port.postMessage({ action: 'refresh' });

这样我们就完成了上述的业务需求。

总结

上述的各种方式都可以实现通知列表页去做刷新动作,不过更推荐使用 window.opener 或 BroadcastChannel 来实现,这两种方式相对使用简单并且很符合这个业务场景。

对于其他需要跨窗口通信的场景,可以根据各个 API 的能力特点来选择使用哪个。

相关推荐
admin and root20 小时前
「AI安全篇」实战AntiDebug自动化JS逆向加解密MCP
javascript·人工智能·网络安全·自动化·漏洞挖掘·cnvd·src赏金
前端snow21 小时前
ai agent --- postgreSQL 关系型数据库
前端
卷无止境1 天前
ECharts:把数据变成故事的可视化利器
前端
Eric_见嘉1 天前
在职前端 Skill 和 MCP 分享
前端·后端·agent
liangshanbo12151 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
wengad1 天前
从 wangEditor 迁到 wangEditor-next
前端·javascript·vue.js
行者-全栈开发1 天前
【前端/JS框架】CVE-2026-3854:GitHub Enterprise Server X-Stat注入XSS漏洞深度剖析与紧急修复指南
前端·javascript·github
计算机魔术师1 天前
AMD砸82亿买下李飞飞,这次不卖芯片卖什么?
前端
hzxpaipai1 天前
杭州企业官网怎么建设?从策划到上线的5步建站方法
运维·服务器·前端·网络
怕浪猫1 天前
2026年金九银十,我面了22个前端
前端·javascript·面试