当前位置:网站首页>跨页面通讯
跨页面通讯
2022-07-05 10:21:00 【码小龙.】
跨页面通讯
前言
你经常会遇到需要跨标签共享信息的情况,那么本文就跟大家一起回顾下web端有哪些方式可以实现这样的需求。
解决方案
websocket
var ws = new WebSocket(“wss://echo.websocket.org”);
ws.onopen = function(evt) {
console.log(“Connection open …”);
ws.send(“Hello WebSockets!”);
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log(“Connection closed.”);
};
参考资料:websocket教程(阮一峰)
localStorage 的监听
localstorge在一个标签页里被添加、修改或删除时,都会触发一个storage事件,通过在另一个标签页里监听storage事件,即可得到localstorge存储的值,实现不同标签页之间的通信。
$(function(){
window.addEventListener(“storage”, function(event){
console.log(event.key );
console.log(event.oldValue);
console.log(event.newValue);
console.log(event.url); //当前发生改变的url
});
});
定时器监听cookie
使用cookie+setInterval,将要传递的信息存储在cookie中,每隔一定时间读取cookie信息,即可随时获取要传递的信息。
$(function(){
setInterval(function(){
var value=cookieUtil.get(‘name’);
console.log(value);
}, 10000);
});
BroadCast Channel – postMessage
适用于同源的跨页面通讯,可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他所有页面收到。它的API和用法都非常简单。
下面的方式就可以创建一个标识为AlienZHOU的频道:
const bc = new BroadcastChannel(‘AlienZHOU’);
各个页面可以通过onmessage来监听被广播的消息:
bc.onmessage = function (e) {
const data = e.data;
const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from;
console.log(’[BroadcastChannel] receive message:‘, text);
};
//要发送消息时只需要调用实例上的postMessage方法即可:
bc.postMessage(mydata);
service worker
Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
首先,需要在页面注册 Service Worker:
/ 页面逻辑 /
navigator.serviceWorker.register(’…/util.sw.js’).then(function () {
console.log(‘Service Worker 注册成功’);
});
其中…/util.sw.js是对应的 Service Worker 脚本。Service Worker 本身并不自动具备“广播通信”的功能,需要我们添加些代码,将其改造成消息中转站:
/* …/util.sw.js Service Worker 逻辑 /
self.addEventListener(‘message’, function (e) {
console.log(‘service worker receive message’, e.data);
e.waitUntil(
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
client.postMessage(e.data);
});
})
);
});
我们在 Service Worker 中监听了message事件,获取页面(从 Service Worker 的角度叫 client)发送的信息。然后通过self.clients.matchAll()获取当前注册了该 Service Worker 的所有页面,通过调用每个client(即页面)的postMessage方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。
处理完 Service Worker,我们需要在页面监听 Service Worker 发送来的消息:
/ 页面逻辑 /
navigator.serviceWorker.addEventListener(‘message’, function (e) {
const data = e.data;
const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from;
console.log(’[Service Worker] receive message:', text);
});
//最后,当需要同步消息时,可以调用 Service Worker 的postMessage方法:
/ 页面逻辑 */
navigator.serviceWorker.controller.postMessage(mydata);
indexDB
消息发送方将消息存至 IndexedDB 中;接收方(例如所有页面)则通过轮询去获取最新的信息。在这之前,我们先简单封装几个 IndexedDB 的工具方法。
打开数据库连接:
function openStore() {
const storeName = ‘ctc_aleinzhou’;
return new Promise(function (resolve, reject) {
if (!(‘indexedDB’ in window)) {
return reject(‘don’t support indexedDB’);
}
const request = indexedDB.open(‘CTC_DB’, 1);
request.onerror = reject;
request.onsuccess = e => resolve(e.target.result);
request.onupgradeneeded = function (e) {
const db = e.srcElement.result;
if (e.oldVersion === 0 && !db.objectStoreNames.contains(storeName)) {
const store = db.createObjectStore(storeName, {keyPath: ‘tag’});
store.createIndex(storeName + ‘Index’, ‘tag’, {unique: false});
}
}
});
}
//存储数据
function saveData(db, data) {
return new Promise(function (resolve, reject) {
const STORE_NAME = ‘ctc_aleinzhou’;
const tx = db.transaction(STORE_NAME, ‘readwrite’);
const store = tx.objectStore(STORE_NAME);
const request = store.put({tag: ‘ctc_data’, data});
request.onsuccess = () => resolve(db);
request.onerror = reject;
});
}
//查询/读取数据
function query(db) {
const STORE_NAME = ‘ctc_aleinzhou’;
return new Promise(function (resolve, reject) {
try {
const tx = db.transaction(STORE_NAME, ‘readonly’);
const store = tx.objectStore(STORE_NAME);
const dbRequest = store.get(‘ctc_data’);
dbRequest.onsuccess = e => resolve(e.target.result);
dbRequest.onerror = reject;
}
catch (err) {
reject(err);
}
});
}
剩下的工作就非常简单了。首先打开数据连接,并初始化数据:
openStore().then(db => saveData(db, null))
//对于消息读取,可以在连接与初始化后轮询:
openStore().then(db => saveData(db, null)).then(function (db) {
setInterval(function () {
query(db).then(function (res) {
if (!res || !res.data) {
return;
}
const data = res.data;
const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from;
console.log(’[Storage I] receive message:', text);
});
}, 1000);
});
//最后,要发送消息时,只需向 IndexedDB 存储数据即可:
openStore().then(db => saveData(db, null)).then(function (db) {
// …… 省略上面的轮询代码
// 触发 saveData 的方法可以放在用户操作的事件监听内
saveData(db, mydata);
});
window.open + window.opener(同源页面)
当我们使用window.open打开页面时,方法会返回一个被打开页面window的引用。而在未显示指定noopener时,被打开的页面可以通过window.opener获取到打开它的页面的引用 —— 通过这种方式我们就将这些页面建立起了联系(一种树形结构)。
首先,我们把window.open打开的页面的window对象收集起来:
let childWins = [];
document.getElementById(‘btn’).addEventListener(‘click’, function () {
const win = window.open(‘./some/sample’);
childWins.push(win);
});
然后,当我们需要发送消息的时候,作为消息的发起方,一个页面需要同时通知它打开的页面与打开它的页面:
// 过滤掉已经关闭的窗口
childWins = childWins.filter(w => !w.closed);
if (childWins.length > 0) {
mydata.fromOpenner = false;
childWins.forEach(w => w.postMessage(mydata));
}
if (window.opener && !window.opener.closed) {
mydata.fromOpenner = true;
window.opener.postMessage(mydata);
}
注意,我这里先用.closed属性过滤掉已经被关闭的 Tab 窗口。这样,作为消息发送方的任务就完成了。下面看看,作为消息接收方,它需要做什么。
此时,一个收到消息的页面就不能那么自私了,除了展示收到的消息,它还需要将消息再传递给它所“知道的人”(打开与被它打开的页面):
需要注意的是,我这里通过判断消息来源,避免将消息回传给发送方,防止消息在两者间死循环的传递。(该方案会有些其他小问题,实际中可以进一步优化)
window.addEventListener(‘message’, function (e) {
const data = e.data;
const text = ‘[receive] ’ + data.msg + ’ —— tab ’ + data.from;
console.log(’[Cross-document Messaging] receive message:', text);
// 避免消息回传
if (window.opener && !window.opener.closed && data.fromOpenner) {
window.opener.postMessage(data);
}
// 过滤掉已经关闭的窗口
childWins = childWins.filter(w => !w.closed);
// 避免消息回传
if (childWins && !data.fromOpenner) {
childWins.forEach(w => w.postMessage(data));
}
});
这样,每个节点(页面)都肩负起了传递消息的责任,也就是我说的“口口相传”,而消息就在这个树状结构中流转了起来。
iframe 非同源页
在我的解决跨域的专题文章中有详细介绍代码方案,其思路如下图:
边栏推荐
猜你喜欢
2022年流动式起重机司机考试题库及模拟考试
学习笔记5--高精地图解决方案
> Could not create task ‘:app:MyTest. main()‘. > SourceSet with name ‘main‘ not found. Problem repair
Pseudo class elements -- before and after
爬虫(9) - Scrapy框架(1) | Scrapy 异步网络爬虫框架
C language QQ chat room small project [complete source code]
Universal double button or single button pop-up
Idea create a new sprintboot project
重磅:国产IDE发布,由阿里研发,完全开源!
学习笔记6--卫星定位技术(上)
随机推荐
How to judge that the thread pool has completed all tasks?
报错:Module not found: Error: Can‘t resolve ‘XXX‘ in ‘XXXX‘
beego跨域问题解决方案-亲试成功
C语言活期储蓄账户管理系统
NAS与SAN
csdn软件测试入门的测试基本流程
想请教一下,十大券商有哪些?在线开户是安全么?
What is the most suitable book for programmers to engage in open source?
数据库中的范式:第一范式,第二范式,第三范式
Glide Mastery
uniapp + uniCloud+unipay 实现微信小程序支付功能
Error: module not found: error: can't resolve 'xxx' in 'XXXX‘
vscode的快捷键
2022年T电梯修理操作证考试题及答案
到底谁才是“良心”国产品牌?
Node の MongoDB Driver
The most complete is an I2C summary
微信核酸检测预约小程序系统毕业设计毕设(7)中期检查报告
StaticLayout的使用详解
WorkManager学习一