浏览器跨标签页通信不会?看了这篇文章你就会了!
前言
为了提高稳定性,每个标签页都是一个独立的浏览器上下文,它们之间是相互隔离的,一个标签页崩溃不会影响到其他标签页,无法直接访问对方的数据或进行通信。
浏览器跨标签页通信是指在同一浏览器的不同Tab标签页或窗口之间进行数据交互,通过跨标签页通信,可以实现数据的共享、状态的同步、消息的传递等功能。
简单说就是,所有的浏览器都支持多标签页的,我们可以在一个浏览器中打开多个标签页,每个标签页访问不同的网站内容,一个标签页能够发送信息给另一个标签页。已下图为例,当a.html
标签页发送一个消息时,b.html
和c.html
可以接收到来自a.html
的消息,这就是跨标签页通信。
在日常项目开发中,虽然不经常用到,但还是很有价值的,遇到一些应用场景时可以信手拈来。
常见场景如下:
- 登录或登出:当用户打开多个标签页时,在某个标签页登录或登出需要通知其它所有标签页更新状态。
- 消息同步:当用户在一个标签页收到新的消息或通知时,需要将这一状态或消息内容传递给所有打开的页面,以实现实时提醒功能。
- 共享资源:在某些场景下,可能需要在多个标签页之间共享某些资源,如网络连接、音频/视频播放器等。
下面就来聊一聊实现浏览器跨标签页通信有哪些方式!
二、localStorage和sessionStorage事件
当一个页面修改了localStorage或sessionStorage,其它页面会收到一个事件提示,修改了localStorage或sessionStorage的页面不会收到这个事件。
// a.html
<button type="button" id="btn">点击</button>
<script type="text/javascript">
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
localStorage.setItem('sendKey', "a向b发送了消息:吃饭了吗?")
console.log("a向b发送了消息:吃饭了吗?")
})
</script>
// b.html
window.addEventListener('storage', function(event) {
console.log(event)
if(event.key === 'sendKey') {
const newValue = event.newValue;
console.log(newValue)
localStorage.setItem('sendKey', newValue)
}
})
当 localStorage 或 sessionStorage 被修改时,将触发 storage 事件,判断对应 key 值是否发生变化。
当 localStorage 或 sessionStorage 不变时,不会触发 storage 事件。
此方法的操作简单,代码简单,不需要一些复杂的状态同步逻辑。
缺点如下:
- 如果用户禁用了浏览的LocalStorage该方案就无效了。
- 当前页的 setItem 不会触发当前页的 storage 事件,只会触发其它窗口的。
- 要通信的标签页必须是同域的。
三、BroadcastChannel
Broadcast Channel,即广播频道接口,客户端通过创建 BroadcastChannel
对象加入广播频道。其构造函数只接受一个参数:频道的名称。如果它是第一个连接到该广播频道名称的客户端,则会创建底层频道。
const bc = new BroadcastChannel("test_channel");
发送消息:在创建的 BroadcastChannel 对象上调用 postMessage()
方法就足够了,该方法接受任何对象作为参数。
bc.postMessage("你吃饭了吗?");
接收消息:发布消息时,会向连接到此频道的每个 BroadcastChannel
对象发送一个 message
事件。可以使用 onmessage
事件处理器。
bc.onmessage = (event) => {
console.log(event);
};
断开频道:要离开频道,请调用对象上的 close() 方法。这会断开对象与底层频道的连接,从而允许垃圾回收。
bc.close();
BroadcastChannel构造函数可以创建一个新的频道,然后各个标签页通过监听和发布消息进行通信。每个标签页都可以创建一个与之前频道同名的BroadcastChannel对象,然后就可以通过postMessage方法来进行发送消息,再通过onmessage事件来接受消息。
// a.html
<button type="button" id="btn">点击</button>
<script type="text/javascript">
const btn = document.getElementById('btn');
// 创建 BroadcastChannel 实例
const bs = new BroadcastChannel('pay');
// 点击事件
btn.addEventListener('click', () => {
// 发送消息
bs.postMessage({ message: '支持成功,刷新状态' });
// 关闭频道
bs.close();
})
</script>
// b.html
// 创建 BroadcastChannel 实例
const bs = new BroadcastChannel('pay');
// 监听频道的消息
bs.onmessage = (event) => {
console.log(event)
}
适用于在同一域下的多个窗口、标签页或 iframe 之间进行实时消息广播。
四、SharedWorker
SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域。
如果要使 SharedWorker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)。
创建一个执行指定 url 脚本的共享 web worker。
const myWorker = new SharedWorker("./worker.js");
如果已经用 addEventListener 监听了 onmessage 事件,则可以使用 start() 方法手动启动端口。
myWorker.port.start();
// a.html
<button type="button" id="btn">点击</button>
<script type="text/javascript">
const btn = document.getElementById('btn');
// 创建一个 SharedWorker
const worker = new SharedWorker('./worker.js');
btn.addEventListener('click', function(e) {
// port.postMessage发送消息
worker.port.postMessage({
status: 'SUCCESS',
message: '支付成功'
});
})
</script>
创建worker.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);
});
};
};
b.html
// b.html
const worker = new SharedWorker('./worker.js');
const port = worker.port;
// onmessage接收消息
port.onmessage = function (event) {
console.log('接收:', event.data);
};
SharedWorker的特点:
- SharedWorker多页面共享线程只限于同一浏览器,而不能跨浏览器。
- 共享实例,多个上下文共享一个worker实例,节省资源。
- 同源策略,SharedWorker 连接到多个不同的页面必须是同源的。
- 双向通信,可以在当前页面通过portMessage发送消息,也可以在当前页面通过onmessage接收消息。
五、Cookie
Cookies 是存储在用户计算机上的小文件,它们通常用于存储用户的偏好设置、跟踪用户会话等。同一个域名下的不同标签页可以共享 Cookies 数据,从而实现跨标签页的通信。
在一个标签页中设置 Cookie,然后在其他标签页中读取该 Cookie,并根据其值执行相应的操作。
设置Cookie:
function setCookie(key, value, d) {
const date = new Date();
date.setTime(date.getDate() + d*24*60*60*1000);
const expires = date.toUTCString();
document.cookie = `${key}=${value};expires=${expires};path=/;domain=${window.location.host}`;
}
setCookie('message', 'SUCCESS', 7)
获取cookie:
function getCookie(cname) {
const name = cname + "=";
const cookieValue = document.cookie.split(';');
for(let i = 0; i < cookieValue.length; i++) {
const str = cookieValue[i].trim();
if (str.indexOf(name) == 0) {
return str.substring(name.length,str.length);
}
}
return "";
}
getCookie('message'); //SUCCESS
由于我们不能实时监听到Cookie的变化,因此在接收消息的标签页中,我们可以创建一个setInterval定时器,每隔一段时间轮询 Cookie 中的数据。 通过解析和处理 Cookie,我们可以获取到存储的消息,并进行相应的处理。
由于 Cookie 的大小有限制,通常为几 KB。如果需要发送的消息较大,可能需要拆分成多个 Cookie 进行存储。
Cookie 默认只能在同一域名下共享。如果需要在不同域名下进行跨标签页通信,需要设置合理的域名domain和路径path。下面就重点介绍一下domain和path的作用。
Cookie的domain属性:表示cookie所在的域,默认为请求的地址,如网址为a.test.com/public/a.html
,那么domain
默认为a.test.com
。
如域A
为a.test.com
,域B为b.test.com
,那么在域A
生产一个令域A
和域B
都能访问的cookie
就要将该cookie
的domain
设置为.test.com
;
如果要在域A生产一个令域A
不能访问而域B
能访问的cookie
就要将该cookie
的domain
设置为b.test.com
。
Cookie
的path
属性:表示cookie
所在的目录,默认为 /
,就是根目录。如在同一个服务器上有目录 /public/
,/public/dir1/
,/public/dir2/
,现设一个cookie A
的path为 /public/
,cookie B
的path
为 /public/dir1/
,那么public
下的所有页面都可以访问到 cookie A
,而 /public/
和 /public/dir2/
的子页面不能访问 cookie B
。这是因为 cookie
能让其path
路径下的页面访问。
六、WebSocket
可以利用服务端做为中介进行跨标签页通信。WebSocket 最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket 使用一个长连接,在客户端和服务器之间保持持久的连接,从而可以实时地发送和接收数据。
sequenceDiagram
客户端->>服务端: UPgrade:websocket
Note right of 服务端: 这时还是HTTP协议,由客户端发起连接
服务端-->>客户端:101 Switching Protocols
Note over 客户端,服务端: 切换协议为WebSocket协议
客户端->>服务端:发送数据
服务端-->>客户端:发送数据
服务端-->>客户端:发送数据
onopen
: 客户端和服务器建立连接后触发,被称为客户端和服务器之间的初始握手。如果接收到open
, 说明已经连接成功,可以进行通信了。
onmessage
:接收到消息时触发。服务器发送给客户端的消息可包括纯文本消息,二进制数据(Blob
消息或者ArrayBuffer
消息)。
onerror
:响应意外故障时触发,在错误之后总是会终止连接。
onclose
:连接关闭时触发。一旦连接关闭后,客户端和服务端将不会再进行消息的收发。也可主动调用close()
方法关闭连接。
// 创建WebSocket对象,并指定服务器的地址
const socket = new WebSocket("ws://your-websocket-server.com");
// 与服务器建立连接:发送消息到服务器
socket.onopen = () => {
console.log("connect: ");
};
// 收到服务器发送的消息:event处理服务器返回的数据
socket.onmessage = (event) => {
console.log("receive: ", event.data);
};
// 连接或通信过程中发生错误
socket.onerror = (event) => {
console.log("errror: ", event.error);
};
// 与服务器断开连接
socket.onclose = (event) => {
console.log("close: ", event.code);
};
WebSocket的应用场景:
WebSocket主要应用于需要实时、双向通信的web应用中,如:
- 即时聊天:构建实时聊天应用,用户可以实时发送和接收消息,实现低延迟、高效的在线交流。
- 金融市场:股票、外汇、期货等金融市场的实时报价、交易提醒。
- 新闻与社交媒体:实时推送通知。
- 物联网(IoT):设备的状态监控与远程控制,如智能家居、工业自动化等。
- 协作工具:在线文档编辑、白板绘图、代码协作等需要多方实时同步内容的应用。
- 游戏:多人在线游戏中的实时状态同步、玩家交互。
- 地理位置服务:实时位置追踪、导航应用中的动态路线更新。
- 直播互动:直播平台的实时评论、弹幕、礼物赠送等互动功能。
- 数据分析与监控:实时仪表盘、日志流处理、性能监控系统的实时数据展示与报警。
总结
选择通信方式:根据具体需求(如是否需要跨域、是否需要实时通信、通信数据的复杂程度等)选择合适的通信方式。
安全性:无论采用哪种方式,都需要注意数据的安全性,特别是当涉及到跨域通信时。
兼容性:考虑到不同浏览器的兼容性,选择兼容性较好的通信方式。
性能:对于需要频繁通信的场景,选择性能较好的通信方式,避免影响用户体验。
在实际应用中,可能会结合使用多种通信方式,以满足不同的需求。例如,可以使用Broadcast Channel
进行实时通知,而使用LocalStorage
进行简单的数据同步。