当前位置:网站首页>跨页面通讯
跨页面通讯
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 非同源页
在我的解决跨域的专题文章中有详细介绍代码方案,其思路如下图:
边栏推荐
- NAS与SAN
- Golang应用专题 - channel
- Timed disappearance pop-up
- Ad20 make logo
- How to judge that the thread pool has completed all tasks?
- AtCoder Beginner Contest 258「ABCDEFG」
- C function returns multiple value methods
- 如何判断线程池已经执行完所有任务了?
- [paper reading] kgat: knowledge graph attention network for recommendation
- 手机厂商“互卷”之年:“机海战术”失灵,“慢节奏”打法崛起
猜你喜欢
随机推荐
beego跨域问题解决方案-亲试成功
非技术部门,如何参与 DevOps?
[paper reading] kgat: knowledge graph attention network for recommendation
"Everyday Mathematics" serial 58: February 27
> Could not create task ‘:app:MyTest.main()‘. > SourceSet with name ‘main‘ not found.问题修复
SAP UI5 ObjectPageLayout 控件使用方法分享
How does redis implement multiple zones?
【JS】数组降维
Pseudo class elements -- before and after
@Serializedname annotation use
非技術部門,如何參與 DevOps?
[JS] array dimensionality reduction
5g NR system architecture
【js学习笔记五十四】BFC方式
C语言活期储蓄账户管理系统
【观察】跨境电商“独立站”模式崛起,如何抓住下一个红利爆发时代?
Shortcut keys for vscode
What are the top ten securities companies? Is it safe to open an account online?
Livedata interview question bank and answers -- 7 consecutive questions in livedata interview~
[dark horse morning post] Luo Yonghao responded to ridicule Oriental selection; Dong Qing's husband Mi Chunlei was executed for more than 700million; Geely officially acquired Meizu; Huawei releases M