第一章 前言
在前端后台管理开发的过程中,我们常常会遇到这样的需求:用户在浏览器中打开了同一个网站的多个标签页,我们需要在这些标签页之间进行通信。比如,当用户在一个标签页中修改了某些数据,我们希望其他标签页能够及时更新。这种跨标签页通信的需求在实际开发中非常常见,但是同时也是经常忽略的一个点,通常说刷一下之后就好了,不用那么麻烦。但是小编为了解决这一问题,来与大家分享一下如何实现前端跨标签页通信。
第二章 需要跨标签通信的原因
在实际的业务场景中,跨标签页通信的需求主要体现在以下几个方面:
-
数据同步:当用户在多个标签页中操作同一个数据时,需要保持数据的一致性。例如,在一个电商网站中,用户可能在多个标签页中查看不同的商品,当用户在一个标签页中将商品加入购物车时,其他标签页的购物车数量也需要及时更新。
-
状态共享:有些应用需要在多个标签页之间共享状态。比如,一个在线文档编辑器,用户可能在多个标签页中打开同一个文档,需要保持文档的编辑状态一致。
-
性能优化:通过跨标签页通信,我们可以避免在每个标签页中重复加载相同的数据,从而提高应用的性能。
第三章 实现跨标签通信的方法以及优缺点
3.1 方法一:BroadcastChannel
- 原理:BroadcastChannel 是浏览器原生支持的跨标签页通讯 API,允许在同一源(origin)下的不同标签页之间进行消息传递。
- 发送与接收端代码如下:
发送端:(小编这里做的是跨页面列表初始化的功能)
javascript
// 创建一个 BroadcastChannel 实例
const channel = new BroadcastChannel('my-channel')
// 发送消息
channel.postMessage({
type: 'init',
data: {
page: 1,
limit: 10
}
})
接收端:
javascript
// 创建一个 BroadcastChannel 实例
const channel = new BroadcastChannel('my-channel')
// 监听消息
channel.onmessage = (event) => {
console.log('Received message:', event.data)
if (event.data.type === 'init') {
const { page, limit } = event.data.data
currentPage.value = page
pageSize.value = limit
// 初始化列表
getList()
}
}
- 优点:
- API 设计简单,易于理解和使用
- 消息是实时传递的
- 即使数据没有改变也会检测到
- 缺点:
- 只能在相同源(协议 + 域名 + 端口)的标签页之间通信
3.2 方法二:localStorage
- 原理:使用localStorage能实现是因为我们开出的新标签页在都是同一个协议、域名、端口号下 ,它们的本地存储内容是共享的!(也就是同源下的不同标签页之间可以共享数据)
- 使用localStorage(看技术栈,如果是vue,也可以用vuex/pinia等,原理是类似的),发送与接收端代码如下:
发送端:
javascript
// 先清除原来数据
localStorage.removeItem('PAGE_INIT')
// 重新赋值(这样做接受端一定能检测到变化)
localStorage.setItem('PAGE_INIT', JSON.stringify({ page: 1, limit: 10 }))
// 注:其实不用跟小编一样,这么处理只要能让本地存储的数据变化就能实现需求
接收端:
javascript
// 监听存储信息
window.addEventListener('storage', (e) => {
if (e.key === 'PAGE_INIT') {
console.log('e.newValue', e)
const { page, limit } = JSON.parse(e.newValue)
console.log('数据',page, limt )
currentPage.value = page
pageSize.value = limit
getList()
}
})
- 优点:
- localStorage浏览器原生自带的,兼容性高,几乎所有浏览器都兼容
- 使用简单,发送消息的窗口不会触发监听事件
- 数据持久化
- 缺点:
- 新标签必须遵守同源协议接受端才能接收到(同源策略)
- 如果监听的值未发生改变也不会触发事件
- 当前标签页不会触发
3.3 方法三:SharedWorker
- 原理:ShareWorker是一种特殊的 Web Worker,允许多个标签页共享同一个 Worker 实例,从而实现跨标签页通信。
- 新建worker文件下添加shared-worker.js文件

javascript
// shared-worker.js
// 每个页面都会创建一个port
const ports = [];
onconnect = (event) => {
console.log('shared-worker onconnect', event);
// 获取当前页的port
const port = event.ports[0];
// 将当前页面的port加入到数组
ports.push(port);
port.onmessage = (event) => {
// 广播消息到所有连接的标签页
ports.forEach((p) => {
if (p !== port) {
p.postMessage(event.data);
}
});
};
};
- 发送与接收端代码如下:
发送端:
javascript
import workerjs from '@/worker/shared-worker.js?url'
// 创建实例
const worker = new SharedWorker(workerjs)
// 发送消息到shared-worker
worker.port.postMessage({
type: 'init',
data: {
page: 1,
size: 10
}
})
接收端:
javascript
import workerjs from '@/worker/shared-worker.js?url'
// 创建实例
const worker = new SharedWorker(workerjs)
// 接收消息
worker.port.onmessage = (event) => {
console.log('shared-worker onmessage', event.data)
if (event.data.type === 'init') {
// 初始化
const {
data: { page, size }
} = event.data
currentPage.value = page
pageSize.value = size
getList()
}
}
- 优点:
- 多个标签页共享同一个 Worker 实例
- 适合需要复杂逻辑处理的场景
- 缺点:
- 部分浏览器或者低版本浏览器可能不支持
- 官方文档
3.4 方法四:Window.postMessage
- 原理:通过 Window.postMessage 可以在不同窗口或标签页之间传递消息。
- 可以看小编下面这篇文章:
(注意:小编在跨标签页通信时,不推荐该方法,可看下面的缺点)
- 优点:
- 可以在不同源的窗口或标签页之间通信
- 可以指定目标窗口的origin,从而确保消息来源可信(安全性)
- 缺点:
- 需要持有目标窗口的引用,即需要开新的标签页,原有标签页没法监听到(理解:目前已有标签页1、标签页2,需要在标签页2做某些处理,标签页1能监听到消息没法实现,只能在标签页2开新的窗口标签页3)
3.5 方法五:Service Worker
- 原理:Service Worker 可以拦截网络请求并缓存资源,同时也可以用于跨标签页通信。
- 发送端与接收端代码如下:
javascript
// 注册 Service Worker
navigator.serviceWorker.register('service-worker.js');
// 发送消息
navigator.serviceWorker.controller.postMessage({
type: 'init',
data: {
page: 1,
limit: 10
}
});
// 接收消息
navigator.serviceWorker.onmessage = (event) => {
console.log('Received message:', event.data);
}
- 优点:
- Service Worker 可以用于离线缓存
- 适合需要复杂逻辑处理的场景
- 缺点:
- 部分浏览器或者低版本浏览器可能不支持
3.6 方法六:IndexedDB
- IndexedDB 是一种浏览器端数据库,可以存储大量结构化数据。通过监听 IndexedDB 的变化,可以实现跨标签页通信
- 该方案小编也没有具体实操过,代码如下:
发送端将信息写入IndexedDB:
javascript
const data = {
type: 'init',
data: {
page: 1,
limit: 10
}
};
const dbName = "simpleDB";
const storeName = "SharedData";
const key = "sharedKey";
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = e => {
const db = e.target.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
};
request.onsuccess = e => {
const db = e.target.result;
console.log("db", db);
const tx = db.transaction(storeName, "readwrite");
const store = tx.objectStore(storeName);
// 保存数据
store.put(data, key);
tx.oncomplete = () => {
// 关闭连接
db.close();
};
};
接收端监听IndexedDB信息变化:
javascript
getDBData() {
const dbName = "simpleDB";
const storeName = "SharedData";
const key = "sharedKey";
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = e => {
const db = e.target.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
};
request.onsuccess = e => {
const db = e.target.result;
const tx = db.transaction(storeName, "readonly");
const store = tx.objectStore(storeName);
const getRequest = store.get(key);
getRequest.onsuccess = () => {
const data = getRequest.result;
db.close();
};
}
}
// 监听数据变化--这里使用setInterval
setInterval(() => {
getDB();
}, 2000);
// 版本号变化监听-onversionchange
db.onversionchange = () => {
console.log('数据库已更新,重新加载数据...');
}
- 优点:
- 适合存储大量数据
- 所有操作都是异步的
- 几乎所有现代浏览器都支持
- 缺点:
- 需要大家具体实操感受
- 注意问题:
- 无痕模式下无法使用indexedDB
- 取决于浏览器用户的主动行为,当用户拒绝访问时,不被允许
3.7 方法七:WebSocket
- 这里不做过多介绍,该方法需要前后端配合,有点"大材小用"
- 有兴趣可以看小编下面这篇文章(基础篇):
第四章 总结
|--------------------|------------------|--------------------------|---------------|
| 方法 | 特点 | 适用场景 | 兼容性 |
| BroadcastChannel | 简单易用,实时性强,同源限制 | 同源标签页之间的实时通信(基本上简单需求都能用) | 低版本ie除外 |
| localStorage | 兼容性好,数据持久化,事件触发 | 简单的状态同步 | 基本上支持 |
| SharedWorker | 共享实例,适合复杂场景 | 需要复杂逻辑处理的场景 | IE 和部分移动浏览器除外 |
| Window.postMessage | 跨域支持,安全性高,需要窗口跳转 | 跨域窗口或标签页之间的通信 | 基本上支持 |
| Service Worker | 离线支持,适合复杂场景 | 需要离线支持的场景 | ie除外 |
| IndexedDB | 大数据支持,异步操作 | 需要存储大量数据的场景 | 基本上支持 |
| WebSocket | 前后端,实时 | 实时通信 | 基本上支持 |