引言
在日常Web开发中,我们需要在不同浏览器标签页之间进行数据传递和时间触发,比如不同标签页面同时切换,另一个标签页将拿到得数据传递回当前页面,或通知原来得页面进行刷新等操作。那么为解决上述问题,我学习并整理了几种浏览器跨标签页通信的解决方案,仅供大家学习和参考!!!
常见方案如下:
- BroadCast Channel
- Service Worker
- Window.onstorage
- window.postMessage
- Shared Worker
- IndexedDB
- Cookie
- Websoket
BroadCast Channel
BroadCast Channel,顾名思义会常见一个同源页面都可以共享的广播频道,当某个页面中发送消息时可以被同源的其他页面监听到,从而实现跨标签也的广播通信。
Broadcast Channel API允许同源(同一站点)的浏览器上下文(包括窗口,标签,框架或iframe)之间的简单通信。
下边我们对Broadcast Channel进行简单的一个封装:
/**
* 简单封装BroadcastChannel的用法
*/
export const Channel = {
/**
* BroadcastChannel对象Map
*/
channelMap: new Map(),
/**
* 发送消息,重载方法,可直接调用,省略对象实例化操作
* @param {*} channelName 通道名称,用以区分不同的通道
* @param {*} object 消息体
*/
send: (channelName, object) => {
if (!Channel.channelMap.has(channelName)) {
let channel = new BroadcastChannel(channelName);
Channel.channelMap.set(channelName, channel);
}
Channel.channelMap.get(channelName).postMessage(object);
},
/**
* 监听消息,重载方法,可直接调用,省略对象实例化操作
* @param {*} channelName 通道名称,用以区分不同的通道
* @param {*} callback 回调函数
*/
listen: (channelName, callback) => {
if (!Channel.channelMap.has(channelName)) {
let channel = new BroadcastChannel(channelName);
Channel.channelMap.set(channelName, channel);
}
Channel.channelMap.get(channelName).onmessage = ({ data }) => {
callback(data);
};
},
/**
* 通道关闭
* @param {*} channelName 通道名称,用以区分不同的通道
*/
close: (channelName) => {
if (Channel.channelMap.has(channelName)) {
Channel.channelMap.get(channelName).close();
Channel.channelMap.delete(channelName);
}
},
/**
* 通道枚举,定义业务中需要用到的所有通道名称枚举,可根据业务需求无限扩容
*/
channelEnum: {
LOGOUT: {name: 'logout',comment: '用户登出系统'},
LOGIN: { name: 'login', comment: '用户登录成功' }
}
};
Service Worker
Service Worker ,一个服务器与浏览器之间的中间人角色,可以实现消息推动、地理围栏、离线应用等功能,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器,从而大大提高浏览体验。
// page1.vue
navgator.serviceWorker.register('./serviceWorker.js'
.then(()=>{
console.log('serviceWorker 注册成功')
})
const buttonClick = ()=>{
navigator.serviceWorker.controller.postMessage('小猪Passion');
}
// page2.vue
navgator.serviceWorker.register('./serviceWorker.js'
.then(()=>{
console.log('serviceWorker 注册成功')
})
navigator.serviceWorker.onmessage = ({ data }) => {
console.log(data)
}
// serviceWorker.js
self.addEventListener("message",async event=>{
const clients = await self.clients.matchAll();
clients.forEach(function(client){
client.postMessage(event.data)
});
});
LocalStorage Window.onstorage
如何利用window.onstorage进行通信呢?我们都知道触发window.onstorage必须满足条件:就是对已有storage进行更新。localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除,故我们采用localStorage 结合 window.onstorage 去完成。
// page1.vue
localStorage.setItem('message', '小猪Passion');
// page2.vue
window.onstorage = (e) => {
if(e.key === 'message'){
console.log(e.newValue)
}
};
注:
- 同一页面不生效:同一页面中修改了
local Storage值,window.onstorage时间不会在当前页面触发;- 初次设置值不生效:
onstorage的触发条件是值得变化,而新增并不被是为值得变化。
window.postMessage
window.postMessage,显而易见也是为了实现不同页面间的相互通信。但此方法可以在不同源的情况下,任意页面之间进行通信,它提供了一种受控机制来规避跨域的限制。该方法也有一定的安全隐患,如果在没有任何限制的情况下,不同源的页面可能会对你进行xss攻击。不过,只要正确的使用,这种方法就很安全。
// 发起通讯
window.opener.postMessage(message,targetOrigin);
// 监听通信
window.addEventListener("message", (e) => { console.log(e); });
注意:
- 如果你不希望从其他网站接收
message,请不要为message事件添加任何事件侦听器;- 如果你确实希望从其他网站接收
message,请始终使用origin和source属性验证发件人的身份;- 当你使用
postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是'*'。
Shared Worker
Shared Worker,是一种在多个浏览器标签页之间共享的 JavaScript 线程,因此也可用来实现跨标签页通信。它可以在同源页面中进行连接,访问窗口、iframe或其他worker。
// use Shared Worker
// 创建一个 Shared Worker
const worker = new SharedWorker('worker.js');
// 监听来自 Shared Worker 的消息
worker.port.onmessage = function(event) {
console.log('Received message from worker:', event.data);
};
// 向 Shared Worker 发送消息
worker.port.postMessage('小猪Passion!!!');
// worker.js
// 监听来自主页面的消息
self.onconnect = function(event) {
const port = event.ports[0];
// 监听来自主页面的消息
port.onmessage = function(event) {
console.log('Received message from main page:', event.data);
// 向主页面发送消息
port.postMessage('Hello from shared worker!');
};
};
IndexedDB
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。
IndexedDB,是一种浏览器提供的本地数据库,可以存在多个不同的标签页中故可以共享数据。
使用场景:
- 数据可视化等界面,大量数据请求会消耗性能;
- 聊天工具等,大量数据需要存储在本地
// 初始化 IndexedDB 写入操作
const dbName = 'crossTabDB';
const storeName = 'messages';
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'id', autoIncrement: true });
}
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
async function writeMessage(message) {
const db = await openDB();
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
store.add({ message, timestamp: Date.now() });
tx.oncomplete = () => {
console.log('Message written:', message);
};
tx.onerror = (event) => {
console.error('Error writing message:', event.target.error);
};
}
// 利用 setInterval 轮询读取消息
let lastTimestamp = 0;
async function readMessages() {
const db = await openDB();
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const request = store.getAll();
request.onsuccess = () => {
const allMessages = request.result;
const newMessages = allMessages.filter(msg => msg.timestamp > lastTimestamp);
if (newMessages.length > 0) {
console.log('New Messages:', newMessages);
lastTimestamp = Math.max(...newMessages.map(msg => msg.timestamp));
}
};
request.onerror = (event) => {
console.error('Error reading messages:', event.target.error);
};
}
// 设置轮询,每隔 1 秒检查新消息
setInterval(readMessages, 1000);
Cookie
同理,Cookie也可以在用于多个不同标签页共享数据。
// 写入消息
function writeMessageToCookie(message, expireSeconds = 5) {
const expireDate = new Date();
expireDate.setTime(expireDate.getTime() + expireSeconds * 1000);
document.cookie = `crossTabMessage=${encodeURIComponent(
message
)}; expires=${expireDate.toUTCString()}; path=/`;
console.log('Message written to cookie:', message);
}
// 读取Cookie
function readMessageFromCookie(name) {
const cookieArr = document.cookie.split(';');
for (let cookie of cookieArr) {
const [key, value] = cookie.split('=').map((item) => item.trim());
if (key === name) {
return decodeURIComponent(value);
}
}
return null;
}
// 轮询读取Cookie 并 处理消息
let lastMessage = null;
function checkForNewMessage() {
const message = readMessageFromCookie('crossTabMessage');
if (message && message !== lastMessage) {
console.log('New Message:', message);
lastMessage = message;
}
}
// 设置轮询,每隔 1 秒检查新消息
setInterval(checkForNewMessage, 1000);
注意:
Cookie通信只能在同一域名下共享;Cookie存储大小有限,通常为几KB;- 安全性问题,
Cookie中的数据可以通过其他途径更改
Websocket
WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。故也可以用来进行跨标签页通信,示例代码如下:
// page1 发送消息
// 创建一个 WebSocket 连接
const socket = new WebSocket('ws://example.com');
// 监听连接成功的事件
socket.onopen = function() {
// 发送消息到服务器
socket.send('小猪Passion');
};
// page2 接收消息
// 创建一个 WebSocket 连接
const socket = new WebSocket('ws://example.com');
// 监听来自服务器的消息
socket.onmessage = function(event) {
console.log('Received message:', event.data);
};
// 服务端
// 创建一个 WebSocket 服务器
const WebSocketServer = require('ws').Server
const wss = new WebSocketServer({ port: 8080 })
// 监听来自客户端的连接
wss.on('connection', function (socket) {
// 监听来自客户端的消息
socket.on('message', function (message) {
console.log('Received message:', message)
// 向所有客户端发送消息
wss.clients.forEach(function (client) {
client.send(message)
})
})
})
小结
BroadcastChannel,叫做“广播频道”,官方文档说,该API是用于同源不同页面之间完成通信的功能。与window.postMessage的区别:BroadcastChannel只能用于同源的页面之间进行通信,而window.postMessage却可以用于任何的页面之间,基于BroadcastChannel的同源策略,它无法完成跨域的数据传输,跨域的情况,我们还是使用window.postMessage来处理。Service Worker是一种在浏览器后台运行的脚本,可以拦截和处理网络请求。通过在Service Worker中监听和处理消息事件,可以实现跨标签页通信。-
window.onstorage监听:通过在不同的标签页中监听LocalStorage的变化,可以实现跨标签页通信。当一个标签页修改LocalStorage的值时,其他标签页可以通过监听storage事件来获取最新值。 - 文章的后四种方式,采用
轮询+的方式来实现跨标签页面通信,但是实际使用场景较少较为复杂,并不推荐。
上述时小弟的一点点理解,希望能一次积累进步,若存在错误请指出,谢谢大家!!!
学习参考:
文章荐读: