highlight: arduino-light theme: channing-cyan
💡 在 Web 开发中,有时我们需要实现不同页面之间的数据传递和事件触发,比如一个页面打开了另一个页面,然后在新的页面中操作后需要更新原来的页面的内容。这种场景在电商、支付、社交等领域都很常见,那么如何用js来实现不同页面之间的交互呢?下面提供几种常见的方法供大家学习参考!
一、onStorage
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、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、
sessionStorage.setItem('message', '我是sessionStorage的值');
// 触发自定义事件来通知其他标签页
var event = new Event('sessionStorageUpdated');
window.dispatchEvent(event);
window.addEventListener('storage', function(event) {
if (event.key === 'message') {
var message = sessionStorage.getItem('message');
console.log('message:', message);
}
});
// 监听自定义事件来检测 `sessionStorage` 的更新
window.addEventListener('sessionStorageUpdated', function() {
var message = sessionStorage.getItem('message');
console.log('message:', message);
});
(2)总结
我们新建两个html分别叫1.html和2.html,并加上上面的js,于是我们每次打开或者刷新该页面就会给a加上1。需要注意的是,如果是双击打开,是在file://协议下的,而且不会触发storage事件,但是会给a加上1,所以可以做一个功能,计算本地某个文件被打开了多少次。如果我们用服务器打开,我们的不同tab页面通信完成了,而且是实时的。
✍️Tips
- 该事件不在导致数据变化的当前页面触发(如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件)。
-
sessionStorage(❎)不能触发 storage 事件 , localStorage(✅)可以。
● 修正:localStorage 和 sessionStorage 对象都会触发 storage 事件,当其他窗口或标签页对存储进行更改时会触发该事件。localStorage 存储的数据是共享的,因此需要小心处理数据的同步和更新,以避免冲突和数据不一致的问题。storage 事件在更新同一 sessionStorage 的标签页之间不会触发。storage 事件无法提供更改的具体值,因此我们需要手动读取 sessionStorage 的值来获取更新的数据。sessionStorage 实现跨Tab页面的通信没有localStorage 那样直接和简单。
● sessionStorage 的作用域限定在当前会话中,关闭窗口或标签页后数据将被清除。即使是同一域名的不同窗口或者标签,他们之间的sessionStorage 数据也是相互隔离的,无法直接共享。每个窗口或者标签都有自己独立的sessionStorage 对象,他们之间的数据互不干扰。 - 如果修改的值未发生改变,将不会触发 onstorage 事件。
- 优点:浏览器支持效果好、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
- 监听消息除了 .onmessage 这种方式,还可以 使用addEventListener来添加’message’监听,
- 关闭除了使用 Broadcast Channel 实例为我们提供的 close 方法来关闭 Broadcast Channel。我们还可取消或者修改相应的’message’事件监听。两者是有区别的:取消’message’监听只是让页面不对广播消息进行响应,Broadcast Channel 仍然存在;而调用 close 方法会切断与 Broadcast Channel 的连接,浏览器才能够尝试回收该对象,因为此时浏览器才会知道用户已经不需要使用广播频道了。
- 兼容性:如果不使用 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 ,通常 noopener 和 noreferrer 会同时设置, 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);
}
// 过滤掉已经关闭的 Tab
windowOpens = windowOpens.filter((window) => !window.closed);
// 避免消息回传
if (windowOpens && !data.tag) {
windowOpens.forEach((window) => window.postMessage(data));
}
});
</script>
</body>
✍️Tips
- 在收集到的 window 对象中,可能有的 Tab 窗口被关闭了,这种情况下的 Tab 不需要进行消息传递。
- 对于接受消息的一方来说,需要继续传递消息,但是这里存在一个问题就是消息回传,可能出现两者之间消息的死循环传递。
- 这种方式,类似击鼓传花,一个传一个,传递的消息从前往后,一条锁链。
- 但是如果页面不是通过一个页面打开的,而且直接打开的,或者从三方网站跳转的,那这条锁链将断开。 所以这种方式基本只做了解,问题太多,可不做参考。
五、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
- Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。所以本质上来说 Service Worker 并不自动具备“广播通信”的功能,需要改造 Service Worker 添加些代码,将其改造成消息中转站。在 Service Worker 中监听了message事件,获取页面发送的信息。然后通过 self.clients.matchAll() 获取当前注册了 Service Worker 的所有页面,通过调用每个的 postMessage 方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。
- 兼容性: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://www.toymoban.com/news/detail-740063.html
https://book.douban.com/subject/5325618/文章来源地址https://www.toymoban.com/news/detail-740063.html
到了这里,关于JavaScript实现跨标签页通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!