跨页面通讯

跨页面通讯
前言
你经常会遇到需要跨标签共享信息的情况,那么本文就跟大家一起回顾下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 非同源页
在我的解决跨域的专题文章中有详细介绍代码方案,其思路如下图:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JackieChan_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值