一、为什么要跨标签页通信
在web开发中,有时会有这样的情况,A页面中打开了B页面,B页面中操作了一些内容,再回到A页面时,需要看到更新后的内容。这种场景在电商、支付、社交等领域经常出现。
二、实现跨标签页通信的几种方式
2.1 localStorage
打开A页面,可以看到localStorage和sessionStorage中都存储了testA:
B页面中,可以获取到localStorage,但是无法获取到sessionStorage:
2.2 BroadcastChannel
BroadcastChannel允许同源(相同的协议、host 以及端口)下浏览器不同窗口订阅它,postMessage方法用于发送消息,message事件用于接收消息。
A页面:
const bc = new BroadcastChannel('test')
bc.postMessage('不去上班行吗?')
B页面:
const bc = new BroadcastChannel('test')
bc.onmessage = (e) => {
console.log(e)
}
A、B两个页面都已经打开了,A页面可以发送消息给B页面,B页面也可以发送消息给A页面:
2.3 postMessage(跨源通信)
2.3.1 自己给自己发送消息
这种方式没有实际作用,只是体会下message事件,和其回调中返回的参数e
http://127.0.0.1:5501/testA.html:
const data = {msg: '不去上班行不行?'};
window.postMessage(data, 'http://127.0.0.1:5501'); // 第二个参数表示哪些窗口能收到事件,可以写'*',但是能确定的最好写确定的
window.addEventListener('message', (e) => {
console.log(e);
console.log(e.data); // {msg: '不去上班行不行?'}
console.log(e.origin); // http://127.0.0.1:5501
console.log(e.source); // window
console.log(e.source === this); // true
});
message参数:
- data就是postMessage发送消息时第一个参数
- origin表示监听的消息是那个标签页发送的
- source表示发送消息的代理对象(window)
2.3.2 A打开B,B发送数据给A
http://127.0.0.1:5501中有A和B两个页面
testA:
window.name = 'A页面';
window.addEventListener('message', (e) => {
console.log(e.data);
console.log(e.origin);
console.log(e.source);
console.log(e.source === this);
});
window.open('http://127.0.0.1:5501/testB.html', 'B页面'); // 第二个参数是设置被打开页面的window.name
testB:
const data = {msg: '我是B页面'};
const opener = window.opener; // 如果当前窗口是由另一个窗口打开的,window.opener保留了那个窗口的引用。如果当前窗口不是由其他窗口打开的,则返回null
opener && opener.postMessage(data, 'http://127.0.0.1:5501');
2.3.3 A打开B并且发送数据给B
testA.html:
window.name = 'A页面';
const data = {name: '我是A页面'};
const open = window.open('http://127.0.0.1:5501/testB.html', 'B页面');
// window.open打开B页面后,B页面并没有被立即加载出来,这里用定时器等待B页面加载出来后再发送消息
setTimeout(() => {
open.postMessage(data, 'http://127.0.0.1:5501');
}, 1000);
testB.html:
window.addEventListener('message', (e) => {
console.log(e.data);
console.log(e.origin);
console.log(e.source);
});
打开A时,会自动打开B,并且给B发送消息:
但是在A页面中是通过定时器去触发消息发送的,这种会有个延时,实际中可以当B页面打开的时候就发送一条消息给A,A接收到消息表明此时B已经加载好了,再发送消息给B
testA.html:
window.name = 'A页面';
const data = {name: '我是A页面'};
const open = window.open('http://127.0.0.1:5501/testB.html', 'B页面');
window.addEventListener('message', (e) => {
console.log(e);
if (e.data === 'B页面初始化') {
open.postMessage(data, 'http://127.0.0.1:5501');
}
});
testB.html:
const opener = window.opener;
opener.postMessage('B页面初始化', 'http://127.0.0.1:5501');
window.addEventListener('message', (e) => {
console.log(e.data);
console.log(e.origin);
console.log(e.source);
});
2.3.4 iframe跨域数据传递
testA.html
<h1>A页面</h1>
<div id="message"></div>
<iframe
src="http://127.0.0.1:5501/testB.html"
frameborder="1"
id="Bframe"
></iframe>
window.name = 'A页面';
const data = {msg: '不上班行不行'};
window.onload = () => {
// const frame = document.querySelector('#Bframe').contentWindow;
const frame = window.frames[0];
frame.postMessage(data, 'http://127.0.0.1:5501');
};
window.addEventListener('message', (e) => {
console.log(e.data);
console.log(e.origin);
console.log(e.source);
const div = document.querySelector('#message');
div.innerHTML = 'A页面接收到:' + e.data.msg;
});
testB.html
<h1>B页面</h1>
<div id="message"></div>
window.name = 'B页面';
window.addEventListener('message', (e) => {
console.log(e.data);
console.log(e.origin);
console.log(e.source);
const div = document.querySelector('#message');
div.innerHTML = 'B页面接收到:' + e.data.msg;
const data = {msg: '不上班你养我啊'};
window.top.postMessage(data, 'http://127.0.0.1:5501');
});
2.4 SharedWorker
- 和BroadcastChannel一样,是必须要在同源窗口下,可以进行数据交互;同样地,postMessage方法用于发送消息,message事件用于接收消息。
- 和BroadcastChannel相比,SharedWorker的优势在于使用一个js文件统一管理状态,如果交互逻辑比较复杂,这点优势会被放大。
2.4.1 效果:
sharedWorker.js
let num = 0;
const workerList = [];
self.addEventListener('connect', (e) => {
const port = e.ports[0];
port.addEventListener('message', (e) => {
num += e.data === 'add' ? 1 : -1;
workerList.forEach((port) => {
// 遍历所有已连接的part,发送消息
port.postMessage(num);
});
});
port.start();
workerList.push(port); // 存储已连接的part
port.postMessage(num); // 初始化
});
testA.html
<p>首页</p>
count:
<span id="count">0</span>
<button id="add">add</button>
<br />
<br />
<iframe src="./testB.html"></iframe>
<script>
if (!!window.SharedWorker) {
const count = document.querySelector('#count');
const add = document.querySelector('#add');
const worker = new SharedWorker('./sharedWorker.js');
worker.port.start();
worker.port.addEventListener('message', (e) => {
count.innerText = e.data;
console.log('首页监听', e);
});
add.addEventListener('click', () => {
worker.port.postMessage('add');
});
}
</script>
testB.html
<p>iframe页面</p>
count:
<span id="count">0</span>
<button id="reduce">reduce</button>
<script>
if (!!window.SharedWorker) {
const count = document.querySelector('#count');
const reduce = document.querySelector('#reduce');
const worker = new SharedWorker('./sharedWorker.js');
worker.port.start();
worker.port.addEventListener('message', (e) => {
count.innerText = e.data;
console.log('iframe监听', e);
});
reduce.addEventListener('click', () => {
worker.port.postMessage('reduce');
});
}
</script>
2.4.5 Service Worker
如果你觉得这篇文章对你有用,可以看看作者封装的库xtt-utils,里面封装了非常实用的js方法。如果你也是vue开发者,那更好了,除了常用的api,还有大量的基于element-ui组件库二次封装的使用方法和自定义指令等,帮你提升开发效率。不定期更新,欢迎交流~