当前位置:网站首页>跨页面通讯
跨页面通讯
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 非同源页
在我的解决跨域的专题文章中有详细介绍代码方案,其思路如下图:
边栏推荐
- Activity enter exit animation
- ConstraintLayout官方提供圆角ImageFilterView
- LSTM应用于MNIST数据集分类(与CNN做对比)
- Usage differences between isempty and isblank
- WorkManager學習一
- [可能没有默认的字体]Warning: imagettfbbox() [function.imagettfbbox]: Invalid font filename……
- Solution of ellipsis when pytorch outputs tensor (output tensor completely)
- 各位大佬,我测试起了3条线程同时往3个mysql表中写入,每条线程分别写入100000条数据,用了f
- 伪类元素--before和after
- SAP UI5 ObjectPageLayout 控件使用方法分享
猜你喜欢
5g NR system architecture
AD20 制作 Logo
Learning II of workmanager
Universal double button or single button pop-up
Constrained layout flow
How can non-technical departments participate in Devops?
Timed disappearance pop-up
Learning note 4 -- Key Technologies of high-precision map (Part 2)
Events and bubbles in the applet of "wechat applet - Basics"
AtCoder Beginner Contest 258「ABCDEFG」
随机推荐
MFC宠物商店信息管理系统
How to write high-quality code?
How did automated specification inspection software develop?
In wechat applet, after jumping from one page to another, I found that the page scrolled synchronously after returning
La vue latérale du cycle affiche cinq demi - écrans en dessous de cinq distributions moyennes
Comparative learning in the period of "arms race"
What is the origin of the domain knowledge network that drives the new idea of manufacturing industry upgrading?
How to plan the career of a programmer?
mongoDB副本集
5g NR system architecture
DDOS攻击原理,被ddos攻击的现象
【观察】跨境电商“独立站”模式崛起,如何抓住下一个红利爆发时代?
数据库中的范式:第一范式,第二范式,第三范式
The horizontally scrolling recycleview displays five and a half on one screen, lower than the average distribution of five
【JS】数组降维
Detailed explanation of the use of staticlayout
Excerpt from "sword comes" (VII)
flex4 和 flex3 combox 下拉框长度的解决办法
A large number of virtual anchors in station B were collectively forced to refund: revenue evaporated, but they still owe station B; Jobs was posthumously awarded the U.S. presidential medal of freedo
php解决redis的缓存雪崩,缓存穿透,缓存击穿的问题