同源页面通信:
方法:Broadcast Channel 【广播】
这个可以查看我另一篇文章有使用案例
同一来源的不同文档(在不同的窗口、选项卡、框架或 iframe 中)之间进行通信
// page1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let bc = null;
// 开启广播
function openBroadcast() {
console.log('开启广播');
bc = new BroadcastChannel('cctv');
// 接收广播
bc.onmessage = function(e) {
alert(e.data);
};
// 异常处理
bc.onmessageerror = function(e) {
console.warn('onmessageerror');
};
}
openBroadcast();
// 关闭广播
function closeBroadcast() {
console.log('关闭广播');
bc.close();
}
// 发消息
function sendMessage() {
bc.postMessage('page1: 咯咯')
}
</script>
// page2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page2</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let bc = null;
// 开启广播
function openBroadcast() {
console.log('开启广播');
bc = new BroadcastChannel('cctv');
// 接收广播
bc.onmessage = function(e) {
alert(e.data);
};
// 异常处理
bc.onmessageerror = function(e) {
console.warn('onmessageerror');
};
}
openBroadcast();
// 关闭广播
function closeBroadcast() {
console.log('关闭广播');
bc.close();
}
// 发消息
function sendMessage() {
bc.postMessage('page2: 哒~')
}
</script>
Service Worker 【广播】
// page1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<p></p>
</body>
</html>
<script>
navigator.serviceWorker.register('sw.js').then(function () {
console.log('Service Worker 注册成功');
})
navigator.serviceWorker.addEventListener('message', function (e) {
const { data } = e;
// 过滤自己发送的消息
if (data.from !== 'page1') {
document.querySelector('p').innerText = `${data.from}:${data.data}`;
}
});
function sendMessage() {
const msg = {
from: 'page1',
data: '咯咯'
}
navigator.serviceWorker.controller.postMessage(msg);
}
</script>
// page2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page2</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<p></p>
</body>
</html>
<script>
navigator.serviceWorker.register('sw.js').then(function () {
console.log('Service Worker 注册成功');
})
navigator.serviceWorker.addEventListener('message', function (e) {
const { data } = e;
// 过滤自己发送的消息
if (data.from !== 'page2') {
document.querySelector('p').innerText = `${data.from}:${data.data}`;
}
});
function sendMessage() {
const msg = {
from: 'page2',
data: '哒~'
}
navigator.serviceWorker.controller.postMessage(msg);
}
</script>
// sw.js
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);
});
})
);
});
localStorage 【共享存储 + 监听】
// page1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
function handleMessage(e) {
if (e.key === 'localMessage') {
const msg = JSON.parse(e.newValue);
alert(msg.data);
}
}
// 开启广播
function openBroadcast() {
console.log('开启广播');
// 在当前页面setItem,storage事件不会触发,另外开个标签页,可以在另一个标签页中触发
window.addEventListener('storage', handleMessage);
}
openBroadcast();
// 关闭广播
function closeBroadcast() {
console.log('关闭广播');
window.removeEventListener('storage', handleMessage)
}
// 发消息
function sendMessage() {
const msg = {
data: 'page1: 咯咯',
stamp: new Date()
}
// 设置相同的内容,storage不会触发,用时间戳解决
window.localStorage.setItem('localMessage', JSON.stringify(msg));
}
</script>
<!DOCTYPE html>
// page2
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page2</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
function handleMessage(e) {
if (e.key === 'localMessage') {
const msg = JSON.parse(e.newValue);
alert(msg.data);
}
}
// 开启广播
function openBroadcast() {
console.log('开启广播');
// 在当前页面setItem,storage事件不会触发,另外开个标签页,可以在另一个标签页中触发
window.addEventListener('storage', handleMessage);
}
openBroadcast();
// 关闭广播
function closeBroadcast() {
console.log('关闭广播');
window.removeEventListener('storage', handleMessage)
}
// 发消息
function sendMessage() {
const msg = {
data: 'page2: 哒~',
stamp: new Date()
}
// 设置相同的内容,storage不会触发,用时间戳解决
window.localStorage.setItem('localMessage', JSON.stringify(msg));
}
</script>
Shared Worker 【共享存储 + 轮询】
无法主动通知,需要轮询获取最新数据
// page1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let worker = null;
let timer = null;
// 开启广播
function openBroadcast() {
console.log('开启广播');
worker = new SharedWorker("sharedWorker.js", 'messageWorker'); // [https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker/SharedWorker]
// 定时轮询,发送 get 指令的消息
timer = setInterval(function () {
worker.port.postMessage({ get: true });
}, 1000);
// 监听 get 消息的返回数据
worker.port.onmessage = function (e) {
console.log(e.data);
}
}
openBroadcast();
function closeBroadcast() {
console.log('关闭广播');
clearInterval(timer);
}
// 发消息
function sendMessage() {
worker.port.postMessage('page1: 咯咯');
}
</script>
// page2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page2</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let worker = null;
let timer = null;
// 开启广播
function openBroadcast() {
console.log('开启广播');
worker = new SharedWorker("sharedWorker.js", 'messageWorker'); // [https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker/SharedWorker]
// 定时轮询,发送 get 指令的消息
timer = setInterval(function () {
worker.port.postMessage({ get: true });
}, 1000);
// 监听 get 消息的返回数据
worker.port.addEventListener('message', (e) => {
console.log(e.data);
}, false);
// addEventListener监听时要手动开始,onmessage时不需要
worker.port.start();
}
openBroadcast();
function closeBroadcast() {
console.log('关闭广播');
clearInterval(timer);
}
// 发消息
function sendMessage() {
worker.port.postMessage('page2: 哒~');
}
</script>
// sharedWorker.js
let data = null;
self.addEventListener('connect', function (e) {
const port = e.ports[0];
port.addEventListener('message', function (event) {
// get 指令则返回存储的消息数据
if (event.data.get) {
data && port.postMessage(data);
}
// 非 get 指令则存储该消息数据
else {
data = event.data;
}
});
port.start();
});
window.opener 【口口相传】
// page1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let wins = []; // 记录打开的子窗口
const openedWin = window.open('page2.html')
wins.push(openedWin);
// 发广播
function sendMessage() {
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 向子级窗口发送消息
if (openingWins.length) {
const msg = {
data: 'page1: 咯咯',
upMsg: false, // 是否向上级窗口发送的消息
}
openingWins.forEach(item => item.postMessage(msg))
}
// 有父级窗口,向父级窗口发消息
if (window.opener && !window.opener.closed) {
const msg = {
data: 'page1: 咯咯',
upMsg: true, // 是否向上级窗口发送的消息
}
window.opener.postMessage(msg);
}
}
// 开启广播
function openBroadcast() {
window.addEventListener('message', function (e) {
const { data, upMsg } = e.data;
alert(data);
// 收到向上传递的消息,有父级窗口,继续向上传递
if (window.opener && !window.opener.closed && upMsg) {
window.opener.postMessage(e.data);
}
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 收到向下传递的消息,有子级窗口,继续向下传递
if (openingWins.length && !upMsg) {
openingWins.forEach(item => item.postMessage(e.data))
}
});
}
openBroadcast()
// 关闭广播
function closeBroadcast() {
// TODO
}
</script>
// page2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page2</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let wins = []; // 记录打开的子窗口
const openedWin = window.open('page3.html')
wins.push(openedWin);
// 发广播
function sendMessage() {
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 向子级窗口发送消息
if (openingWins.length) {
const msg = {
data: 'page2: 咯咯',
upMsg: false, // 是否向上级窗口发送的消息
}
openingWins.forEach(item => item.postMessage(msg))
}
// 有父级窗口,向父级窗口发消息
if (window.opener && !window.opener.closed) {
const msg = {
data: 'page2: 咯咯',
upMsg: true, // 是否向上级窗口发送的消息
}
window.opener.postMessage(msg);
}
}
function openBroadcast() {
window.addEventListener('message', function (e) {
const { data, upMsg } = e.data;
alert(data);
// 收到向上传递的消息,有父级窗口,继续向上传递
if (window.opener && !window.opener.closed && upMsg) {
window.opener.postMessage(e.data);
}
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 收到向下传递的消息,有子级窗口,继续向下传递
if (openingWins.length && !upMsg) {
openingWins.forEach(item => item.postMessage(e.data))
}
});
}
openBroadcast()
// 关闭广播
function closeBroadcast() {
// TODO
}
</script>
// page3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page3</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>
<script>
let wins = []; // 记录打开的子窗口
// 发广播
function sendMessage() {
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 向子级窗口发送消息
if (openingWins.length) {
const msg = {
data: 'page3: 咯咯',
upMsg: false, // 是否向上级窗口发送的消息
}
openingWins.forEach(item => item.postMessage(msg))
}
// 有父级窗口,向父级窗口发消息
if (window.opener && !window.opener.closed) {
const msg = {
data: 'page3: 咯咯',
upMsg: true, // 是否向上级窗口发送的消息
}
window.opener.postMessage(msg);
}
}
function openBroadcast() {
window.addEventListener('message', function (e) {
const { data, upMsg } = e.data;
alert(data);
// 收到向上传递的消息,有父级窗口,继续向上传递
if (window.opener && !window.opener.closed && upMsg) {
window.opener.postMessage(e.data);
}
const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面
// 收到向下传递的消息,有子级窗口,继续向下传递
if (openingWins.length && !upMsg) {
openingWins.forEach(item => item.postMessage(e.data))
}
});
}
openBroadcast()
// 关闭广播
function closeBroadcast() {
// TODO
}
</script>
非同源页面通信
iframe 【代理】
父A = 非同源应用A (e.g. http://localhost:63342/demo/iframe/page1.html)
子A = 非同源iframe桥(e.g. http://localhost:8081/)
父B = 非同源应用B (e.g. http://192.168.2.112:3000/)
子B = 非同源iframe桥 (e.g. http://localhost:8081/)
1. 所有消息都先发给iframe桥
const bridgeIframe = document.getElementById('bridgeIframe');
const msg = {
type: 'bridge-msg',
data: 'page1: 咯咯'
};
bridgeIframe.contentWindow.postMessage(msg, '*');
// window.frames[0].window.postMessage(msg, '*');
2. iframe接收后发起同源广播,同源广播可以在另一个应用中监听
// 接收iframe消息
window.addEventListener('message', (e) => {
const { data } = e;
// 转发消息,消息会进入另一个跨域应用的iframe桥的bc.onmessage中
if (data.type === 'bridge-msg') {
this.bc.postMessage(`${data.data}`)
}
});
3. iframe通过窗口对象转发消息
window.parent.postMessage(e.data, '*');
完整代码:
// page1 纯html页面,webstorm服务器运行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page1</title>
</head>
<body>
<span></span>
<button onclick="sendMessage()">发广播</button>
</body>
</html>
<script>
// 发消息
function sendMessage() {
const bridgeIframe = document.getElementById('bridgeIframe');
const msg = {
type: 'bridge-msg',
data: '咯咯'
};
bridgeIframe.contentWindow.postMessage(msg, '*');
}
// 创建桥梁
function createBridge() {
const bridgeIframe = document.createElement('iframe');
bridgeIframe.setAttribute('id', 'bridgeIframe');
// bridgeIframe.style.display = 'none';
bridgeIframe.src = 'http://localhost:8081/';
document.body.appendChild(bridgeIframe);
}
createBridge();
// 监听iframe桥发来的消息
window.addEventListener('message', function(e) {
const { data, origin } = e;
document.querySelector('span').innerText = `${origin}: ${data}`;
});
</script>
// bridgeIframe Vue2应用 http://localhost:8081/
<button @click="sendMessage">发广播</button>
<script>
export default {
name: 'App',
data() {
return {
bc: null,
}
},
methods: {
// 开启广播
openBroadcast() {
this.bc = new BroadcastChannel('cctv');
// 接收广播
// bc广播是同源广播,因为两个跨域应用
this.bc.onmessage = (e) => {
console.log(`iframe桥接收到的消息:${e.data}`);
window.parent.postMessage(e.data, '*');
};
// 异常处理
this.bc.onmessageerror = function(e) {
console.warn('onmessageerror');
};
}
},
mounted() {
this.openBroadcast();
// 接收iframe消息
window.addEventListener('message', (e) => {
const { data } = e;
// 转发消息,消息会进入另一个跨域应用的iframe桥的bc.onmessage中
if (data.type === 'bridge-msg') {
this.bc.postMessage(`${data.data}`)
}
});
}
}
</script>
// page2 React应用 http://192.168.2.112:3000
import './App.css';
import { useEffect, useState } from "react";
function App() {
const [msg, setMsg] = useState('');
useEffect(() => {
createBridge();
// 监听iframe桥发来的消息
window.addEventListener('message', function (e) {
const { data, origin } = e;
setMsg(`${origin}: ${data}`);
});
}, [])
// 创建桥梁
const createBridge = () => {
const bridgeIframe = document.createElement('iframe');
bridgeIframe.setAttribute('id', 'bridgeIframe');
// bridgeIframe.style.display = 'none';
bridgeIframe.src = 'http://localhost:8081/';
document.body.appendChild(bridgeIframe);
}
// 发消息
const sendMessage = () => {
const bridgeIframe = document.getElementById('bridgeIframe');
const msg = {
type: 'bridge-msg',
data: '哒~'
};
bridgeIframe.contentWindow.postMessage(msg, '*');
}
return (
<div className="App">
{ msg }
<button onClick={sendMessage}>发广播</button>
</div>
);
}
export default App;