JavaScript实现跨标签页通信


highlight: arduino-light
theme: channing-cyan

💡 在 Web 开发中,有时我们需要实现不同页面之间的数据传递和事件触发,比如一个页面打开了另一个页面,然后在新的页面中操作后需要更新原来的页面的内容。这种场景在电商、支付、社交等领域都很常见,那么如何用js来实现不同页面之间的交互呢?下面提供几种常见的方法供大家学习参考!

一、localStorage

localstorage是浏览器同域标签共用的存储空间,所以可以用来实现多标签之间的通信。html5出现了一个事件: onstorage,我们在window对象上添加监听就可以监听到变化:

window.addEventListener(‘storage’, (e) => console.log(e));所以在 Web Storage 中,每一次将一个值存储到本地存储时,都会触发一个 storage 事件,通过localStorage 结合window.addEventListener(‘storage’, cb) 完成 A、B 标签页间通信。

(1)代码实例

下面我们用具体的代码实际看一下:

需要注意,此事件是非当前页面对localStorage进行修改时才会触发,当前页面修改localStorage不会触发监听函数。如果实在是要,自己重写一个方法吧。

<!-- 1.html -->
<script>if(!localStorage.getItem('a')){localStorage.setItem('a',1)}else{var sum = localStorage.getItem('a')localStorage.setItem('a', +sum + 1)}
</script><!-- 2.html  -->
<script>window.addEventListener('storage', (e) => console.log(e))
</script>

(2)总结

我们新建两个html分别叫1.html和2.html,并加上上面的js,于是我们每次打开或者刷新该页面就会给a加上1。需要注意的是,如果是双击打开,是在file://协议下的,而且不会触发storage事件,但是会给a加上1,所以可以做一个功能,计算本地某个文件被打开了多少次。如果我们用服务器打开,我们的不同tab页面通信完成了,而且是实时的。
在这里插入图片描述

✍️Tips

  1. 该事件不在导致数据变化的当前页面触发(如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件)。
  2. sessionStorage(❎)不能触发 storage 事件 , localStorage(✅)可以。
  3. 如果修改的值未发生改变,将不会触发 onstorage 事件。
  4. 优点:浏览器支持效果好、API直观、操作简单。缺点:部分浏览器隐身模式下,无法设置 localStorage。如safari,这样也就导致 onstrage 事件无法使用。

除开少数情况,localStorage的兼容性不错,就当前国内的情况,已经基本没有问题了。localStorage 的原理很简单,浏览器为每个域名划出一块本地存储空间,用户网页可以通过 localStorage 命名空间进行读写。

二、BroadcastChannel

BroadcastChannel通信方式的原理就是一个命名管道(频道),可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab页,frame或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的BroadcastChannel 对象。

说到 BroadCast Channel 不得不说一下 postMessage,他们二者的最大区别就在于 postMessage 更像是点对点的通信,而 BroadCast Channel 是广播的方式,点到面。每个 BroadcastChannel 对象都需要使用一个唯一的名称来标识通道,这个名称在同一域名下的不同页面之间必须是唯一的,它允许同一域名下的不同页面之间进行通信。通过 postMessage 方法,一个页面可以将消息发送到频道中,而其他页面则可以监听 message 事件来接收这些消息。通过这种方式是短线了一种实时通信的机制,可以在不同的页面之间传递信息,实现页面间的即时交流。如下图所示:

(1)示例图

(2)代码实例

<!-- 示例1 -->
<!-- A.html -->
<script>const bc = new BroadcastChannel('test_channel');bc.postMessage('This is a test message.');
</script>
<!-- B.html -->
<script>const bc = new BroadcastChannel('test_channel');bc.onmessage = (event) => {console.log(event);};
</script>

<!-- 示例2 -->
<!-- 1.html -->
<body><button id="l-btn">发送消息</button><button id="s-btn">关闭</button><script>// 创建const broadcastChannel = new BroadcastChannel('channelName');// 监听消息broadcastChannel.onmessage = function (e) {console.log('监听消息:', e.data);};document.getElementById('l-btn').onclick = function () {// 发送消息broadcastChannel.postMessage('测试,传送消息,我发送消息啦。。。');};document.getElementById('s-btn').onclick = function () {// 关闭broadcastChannel.close();};</script>
</body>
<!-- 2.html -->
<script>// 创建const broadcastChannel = new BroadcastChannel('channelName');// onmessage监听消息broadcastChannel.onmessage = function (e) {console.log('我是通过onmessage监听的消息:', e.data);};// addEventListener 监听broadcastChannel.addEventListener('message', function (e) {console.log('我是通过addEventListener监听的消息:', e)})
</script>

✍️Tips

  1. 监听消息除了 .onmessage 这种方式,还可以 使用addEventListener来添加’message’监听,
  2. 关闭除了使用 Broadcast Channel 实例为我们提供的 close 方法来关闭 Broadcast Channel。我们还可取消或者修改相应的’message’事件监听。两者是有区别的:取消’message’监听只是让页面不对广播消息进行响应,Broadcast Channel 仍然存在;而调用 close 方法会切断与 Broadcast Channel 的连接,浏览器才能够尝试回收该对象,因为此时浏览器才会知道用户已经不需要使用广播频道了。
  3. 兼容性:如果不使用 IE 和 sf on iOS 浏览器,兼容性还是可以的。

三、postMessage

postMessage 是 H5 引入的 API,该方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案。

(1)语法

(2)代码实例

<!-- 1.html -->
<body><button class="pop">弹出新窗口</button><button class="button">发送数据</button><script>const pop = document.querySelector('.pop');const button = document.querySelector('.button');let index = 0;let opener = null;pop.addEventListener('click', () => {opener = window.open('2.html','123','height=600,width=600,top=20,resizeable=yes');});button.addEventListener('click', () => {const data = {value: `moment ${index++}`,};opener.postMessage(data, '*');});</script></body><!-- 2.html --><body><div>postMessage  2.html</div><script>window.addEventListener("message", (e) => {console.log(e.data);});</script>
</body>

(3)总结

通过点击按钮在主窗口和弹出的新窗口之间进行通信。通过 postMessage,主窗口可以向新窗口发送数据,从而实现了简单的跨窗口通信。在实际应用中,你可以在接收消息的窗口中监听 message 事件,然后在事件处理程序中处理接收到的数据。

具体代码运行效果如下图所示:
在这里插入图片描述

四、open & opener

当我们 系统中通过 window.open 打开一个新页面时,window.open 方法会返回一个被打开页面的引用,而被打开页面则可以通过 window.opener 获取到打开它的页面的引用(当然这是在没有指定noopener的情况下)。

✍️「扩展」- noopener

我们在系统中经常会这样使用 a 标签跳转到第三方网站,有时,当您单击网站上的链接时,该链接将在新选项卡中打开,但旧选项卡也会被重定向到其他网络钓鱼网站,它会要求您登录或开始将一些恶意软件下载到您的设备。这样存在一定的安全隐患,此时在新打开的页面中可通过 window.opener 获取到源页面的 window 对象, 这就埋下了安全隐患。 比如:

  • 你自己的网站 A,点击如上链接打开了第三方网站 B。
  • 此时网站 B 可以通过 window.opener 获取到 A 网站的 window 对象。
  • 然后通过 window.opener.location.href = ‘www.baidu.com’ 这种形式跳转到一个钓鱼网站,泄露用户信息。

为了避免这样的问题,可以添加引入了 rel=“noopener” 属性, 这样新打开的页面便获取不到来源页面的 window 对象了, 此时 window.opener 的值是 null。

但是由于一些老的浏览器并不支持 noopener ,通常 noopenernoreferrer 会同时设置, rel=“noopener noreferrer” 。


回到主题,使用 window.opener 如何实现跨页面通信!

(1)代码实例

  • 发送消息

  • 收集\获取消息

(2)总结

完整代码示例:

<!-- 1.html -->
<body><button id="tab">新开 Tab</button><button id="l-btn">发送消息</button><script>// 单个// 发送消息:单个页面// const pop = document.querySelector('#tab');// const button = document.querySelector('#l-btn');// const data = {};// let windowOpen = null;// pop.addEventListener('click', () => {//   windowOpen = window.open(//     'tab.html',//     '123',//     'height=600,width=600,top=20,resizeable=yes'//   );// });// button.addEventListener('click', () => {//   data.message = '测试,传送消息,我发送消息啦。。。';//   windowOpen.postMessage(data, '*');// });// // 收集 window 对象:多个打开页面,打开一个页面就需要将打开的 window 对象收集起来,以便于发布广播let windowOpens = [];document.getElementById('tab').onclick = function () {// IP 地址为本地的服务const windowOpen = window.open('tab.html');windowOpens.push(windowOpen);};document.getElementById('l-btn').onclick = function () {const data = {};console.log(windowOpens);// 发送消息之前,先进行已关闭 Tab 的过滤windowOpens = windowOpens.filter((window) => !window.closed);if (windowOpens.length > 0) {// 数据打一个标记data.tag = false;data.message = '测试,传送消息,我发送消息啦。。。';windowOpens.forEach((window) => window.postMessage(data, '*'));}if (window.opener && !window.opener.closed) {data.tag = true;window.opener.postMessage(data, '*');}};</script></body><!-- tab.html --><body><script>// 多个let windowOpens = [];window.addEventListener('message', function (e) {const data = e.data;console.log('我接受到消息了:', data.message);// 避免消息回传if (window.opener && !window.opener.closed && data.tag) {window.opener.postMessage(data);}// 过滤掉已经关闭的 TabwindowOpens = windowOpens.filter((window) => !window.closed);// 避免消息回传if (windowOpens && !data.tag) {windowOpens.forEach((window) => window.postMessage(data));}});</script></body>

✍️Tips

  1. 在收集到的 window 对象中,可能有的 Tab 窗口被关闭了,这种情况下的 Tab 不需要进行消息传递。
  2. 对于接受消息的一方来说,需要继续传递消息,但是这里存在一个问题就是消息回传,可能出现两者之间消息的死循环传递。
  3. 这种方式,类似击鼓传花,一个传一个,传递的消息从前往后,一条锁链。
  4. 但是如果页面不是通过一个页面打开的,而且直接打开的,或者从三方网站跳转的,那这条锁链将断开。 所以这种方式基本只做了解,问题太多,可不做参考。

五、SharedWorker

Shared Worker 是一种在多个浏览器标签页之间共享的 JavaScript 线程。它可以用于实现跨标签页的通信。

SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,如不同的浏览器标签页之间共享数据和执行代码。它可以用于在多个浏览上下文之间建立通信通道,以便它们可以共享信息和协同工作。例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域, SharedWorkerGlobalScope。与普通的 Worker 不同,SharedWorker 可以在多个浏览上下文中实例化,而不仅限于一个单独的浏览器标签页或框架。这使得多个浏览上下文可以共享同一个后台线程,从而更有效地共享数据和资源,而不必在每个标签页或框架中都创建一个独立的工作线程。

(1)代码实例

<!-- a.html -->
<script>let index = 0;const worker = new SharedWorker("worker.js");setInterval(() => {worker.port.postMessage(`moment ${index++}`);}, 1000);
</script><!-- b.html -->
<script>const worker = new SharedWorker("worker.js");worker.port.start();setInterval(() => {worker.port.postMessage("php是世界上最好的语言");}, 1000);worker.port.onmessage = function (e) {if (e.data) {console.log(e.data);}};
</script>

创建一个 worker.js 文件,并编写以下代码:

let data = "";self.onconnect = (e) => {const port = e.ports[0];port.onmessage = function (e) {if (e.data === "php是世界上最好的语言") {port.postMessage(data);data = "";} else {data = e.data;}};
};

(2)总结

✍️Tips

备注: 如果要使 SharedWorker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)。

  • Shared Worker 的最大问题在于实现跨页面通信时的,它无法主动通知所有页面,需要刷新页面或者是定时任务来检查是否有新的消息,也就是需要配合轮询来使用。
  • sharedWorker.js 不能使用 .addEventListener 来监听 message 事件,监听无效。 兼容性一般。

六、Service Worker

Service Worker 它是一种服务工作线程,是一种在浏览器背后运行的脚本,用于处理网络请求和缓存等任务。它是一种在浏览器与网络之间的中间层,允许开发者拦截和控制页面发出的网络请求,以及管理缓存,从而实现离线访问、性能优化和推送通知等功能。

它在浏览器背后独立运行与网页分开,这意味着即使用户关闭了网页,Service Worker 仍然可以运行。可以用于实现推送通知功能。它可以注册为推送消息的接收者,当服务器有新的通知要发送时,Service Worker 可以显示通知给用户,即使网页没有打开。

(1)代码实例

要想使用,首先我们创建两个不同的 html 文件分别代表不同的页面,创建一个 Service Worker 文件,并且使用 live server 开启一个本地服务器:

<!-- a.html --><!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>navigator.serviceWorker.register("worker.js").then(() => {console.log("注册成功");});setInterval(() => {navigator.serviceWorker.controller.postMessage({value: `moment ${new Date()}`,});}, 3000);navigator.serviceWorker.onmessage = function (e) {console.log(e.data.value);};</script></body>
</html><!-- b.html --><!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>navigator.serviceWorker.register("worker.js").then(() => {console.log("注册成功");});setInterval(() => {navigator.serviceWorker.controller.postMessage({value: `moment ${new Date()}`,});}, 3000);navigator.serviceWorker.onmessage = function (e) {console.log(e.data.value);};</script></body>
</html>

创建一个 worker.js 文件并编写以下代码:

// worker.js
self.addEventListener("message", function (e) {e.waitUntil(self.clients.matchAll().then(function (clients) {if (!clients || clients.length === 0) {return;}clients.forEach(function (client) {client.postMessage(e.data);});}));
});

代码运行如下图所示:

(2)总结

Service Worker 将遵守以下生命周期:

  • 注册: 在网页的 JavaScript 代码中调用 navigator.serviceWorker.register() 方法来注册一个 Service Worker;
  • 安装: 当 Service Worker 文件被下载并首次运行时,会触发 install 事件。在 install 事件中,你可以缓存静态资源,如 HTML、CSS、JavaScript 文件,以便在离线时使用;
  • 激活: 安装成功后,Service Worker 并不会立即接管页面的网络请求。它需要等到之前的所有页面都关闭,或者在下次页面加载时才会激活();
  • 控制: 一旦 Service Worker 被激活,它就开始控制在其作用域内的页面。它可以拦截页面发出的网络请求,并根据缓存策略返回缓存的内容;
  • 更新: 当你更新 Service Worker 文件并再次注册时,会触发一个新的 install 事件。你可以在新的 install 事件中更新缓存,然后在下次页面加载时进行激活,以确保新的 Service Worker 被使用;
  • 解除注册: 如果你不再需要 Service Worker,可以通过调用 navigator.serviceWorker.unregister() 来解除注册;

Service Worker是一个由 promise 封装的对象,未初始化时是一个 pending 状态的,当成功注册之后会变成 fulfilled,并且对外暴露以下方法,如下图所示:

✍️Tips

  1. Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。所以本质上来说 Service Worker 并不自动具备“广播通信”的功能,需要改造 Service Worker 添加些代码,将其改造成消息中转站。在 Service Worker 中监听了message事件,获取页面发送的信息。然后通过 self.clients.matchAll() 获取当前注册了 Service Worker 的所有页面,通过调用每个的 postMessage 方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。
  2. 兼容性:IE 全军覆没,其他浏览器还行,整体来说一般。

七、cookie

Cookie 的话很熟悉了,它是一种在浏览器和服务器之间传递的小型文本文件,可以用于在多个标签页之间共享数据。可以使用 setInterval 定时轮询 Cookie 来实现跨标签页通信。下面是一个示例:

(1)代码实例

  • 在发送消息的标签页中:
// 设置一个 Cookie,将消息存储在 Cookie 中
let index = 0;
setInterval(() => {document.cookie = `supper=moment ${index++}`;
}, 1000);
  • 在接收消息的标签页中:
<script>console.log("cookie 的值为: ", document.cookie);setInterval(() => {console.log("cookie 的值发生了变化: ", document.cookie);}, 1000);
</script>

(2)总结

  • 跨域名通信:Cookie 默认只能在同一域名下共享。如果需要在不同域名下进行跨标签页通信,需要设置合适的域名和路径。
  • Cookie 大小限制:Cookie 的大小有限制,通常为几 KB。如果消息较大,可能需要拆分成多个 Cookie 进行存储。
  • 安全性考虑:Cookie 中的数据可以被用户和其他脚本访问和修改。因此,不适合存储敏感信息。 以上示例提供了一个基本的框架来演示如何使用 Cookie 实现跨标签页通信。在实际应用中,您可能需要更复杂的逻辑来处理跨标签页通信,并确保数据同步和一致性。

总结

在上面列举了几种前端跨页面通信的方式,当然对前端来说远远不止这五种方式,还有其他方案例如:使用 hashchange、indexDB、Websocket 都是可以的,当前只是列举了部分。对于同源页面,常见的方式包括:

  • 广播模式:Broadcast Channel / Service Worker / LocalStorage + StorageEvent
  • 共享存储模式:Shared Worker / IndexedDB / cookie
  • 口口相传模式:window.open + window.opener
  • 基于服务端:Websocket / Comet / SSE 等

而对于非同源页面,则可以通过嵌入同源 iframe 作为“桥”,将非同源页面通信转换为同源页面通信。

1、不论是 Broadcast Channel,还是 Service Worker ,或是 storage 事件,其都是“广播模式”:一个页面将消息通知给一个“中央站”,再由“中央站”通知给各个页面。且onstorage、 BroadcastChannel、 Service Worker、 Open&Opener、SharedWorker 都是针对同源的 Tab。

2、对于前端跨页面通信一般选择的解决方案是使用 onstorage,主要考量的三个方面:

  • 兼容性。浏览器支持度。
  • 通用性。能否覆盖需求、是否具有拓展性。
  • 便捷性。开发便捷程度。

其他方案在这三个方面来说都或多或少存在一些美中不足。

参考文献

1、window属性:onstorage_w3cschool

2、storage - Web API 接口参考 | MDN

3、面试官:前端跨页面通信,你知道哪些方法? - 掘金

4、各类“服务器推”技术原理与实例(Polling/COMET/SSE/WebSocket) - 掘金

5、Service Worker ——这应该是一个挺全面的整理-CSDN博客

6、SharedWorker - Web APIs | MDN

7、【Service_Worker_API DEV】

8、BroadcastChannel - Web APIs | MDN

好书推荐

用最少的时间,参透高效能人士的持续成功之路。

https://book.douban.com/subject/5325618/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/157433.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【向生活低头】win7打印机共享给win11使用,win11无法连接问题的解决

打印机是跟win7的电脑连接的&#xff0c;然后试了很多方法&#xff0c;win11都没法添加该打印机去使用。 网上的方法乱七八糟啥都有&#xff0c;但试了以后&#xff0c;发现基本没什么用。 刚刚发现知乎上的一个回答是有用的&#xff0c;这里做记录以备后用。 1.打开控制面板的…

windows搭建Cobalt strike

使用cobaltstrike 3.14版本 window10搭建服务器 默认端口可以修改的 window10搭建客户端 双击客服端bat运行连接 监听器 windows/beacon为内置监听器&#xff0c;包括dns、http、https、smb、tcp、extc2六种方式的监听器&#xff1b;windows/foreign为外部监听器 wndows/be…

基于联合表示学习、用户聚类和模型自适应的个性化联合推荐

[Personalized Federated Recommendation via Joint Representation Learning, User Clustering, and Model Adaptation] (https://dl.acm.org/doi/abs/10.1145/3511808.3557668) CIKM2022(CCF-B) 论文精读 文章主要创新点(消融实验分析的三个点)&#xff1a; 联合表示学习 …

用友GRP-U8 任意文件上传漏洞

用友GRP-U8 任意文件上传漏洞 漏洞描述漏洞影响漏洞危害网络测绘Fofa: app"用友-GRP-U8" 漏洞复现1. 构造poc2. 复现3. 访问webshell 漏洞描述 用友GRP-U8是面向政府及行政事业单位的财政管理应用。 漏洞影响 用友 GRP-U8 漏洞危害 用友 GRP-U8 UploadFileData接…

springboot和flask整合nacos,使用openfeign实现服务调用,使用gateway实现网关的搭建(附带jwt续约的实现)

环境准备&#xff1a; 插件版本jdk21springboot 3.0.11 springcloud 2022.0.4 springcloudalibaba 2022.0.0.0 nacos2.2.3&#xff08;稳定版&#xff09;python3.8 nacos部署&#xff08;docker&#xff09; 先创建目录&#xff0c;分别创建config&#xff0c;logs&#xf…

计算机数据库中了locked勒索病毒怎么解决,勒索病毒解密,数据恢复

网络的发展为企业的生产生活提供了极大的便利&#xff0c;但是&#xff0c;随之而来的网络安全威胁也不断增加&#xff0c;从11月份以来&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计算机数据库遭到了locked勒索病毒攻击&#xff0c;所有数据库中的…

sql-50练习题11-15

sql-50练习题11-15 前言数据库表结构介绍学生表课程表成绩表教师表 1-1 查询没有学全所有课程的同学的信息1-2 查询至少有一门课与学号为01的同学所学相同的同学的信息1-3 查询和1号的同学学习的课程完全相同的其他同学的信息1-4 查询没学过张三老师讲授的任一门课程的学生姓名…

Nginx简介,Nginx搭载负载均衡以及Nginx部署前端项目

目录 一. Nginx简介 Nginx的优点 二. Nginx搭载负载均衡 2.1 Nginx安装 2.1.1 安装依赖 2.1.2 解压nginx安装包 2.1.3 安装nginx 2.1.4 启动nginx服务 2.2 tomcat负载均衡 2.3 Nginx配置 三. Nginx前端部署 一. Nginx简介 NGINX&#xff08;读作&#xff1a;engi…

Ant Design Vue Table 表格内容高度自适应+固定表头踩坑

前言 对于非专业前端开发者来使用 Ant Design UI 组件来开发企业级后台管理系统来说是非常不错的选择&#xff0c;但这并不意味着我们能够用好这个框架&#xff0c;因为 UI 交互上和有许多细节上的问题对于非专业前端来说并不容易解决&#xff0c;最近在使用 Table 组件时就遇…

echart 饼图怎么让图形铺满整个div

1.原效果&#xff08;未铺满&#xff09;&#xff1a;原配置 2.如果想要铺满&#xff0c;需要设置radius &#xff0c;radius的意思是 第一个元素为内环半径&#xff0c;第二个参数为外环半径&#xff1b; 如果想要填满的话直接写[0,100%],不过第一个为0后就不是圆环里&#…

FPGA实现HDMI转LVDS视频输出,纯verilog代码驱动,提供4套工程源码和技术支持

目录 1、前言免责声明 2、目前我这里已有的图像处理方案3、本 LVDS 方案的特点4、详细设计方案设计原理框图视频源选择静态彩条IT6802解码芯片配置及采集ADV7611解码芯片配置及采集silicon9011解码芯片配置及采集纯verilog的HDMI 解码模块奇偶场分离并串转换LVDS驱动 5、vivado…