同源页面通信:
方法: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.jsself.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:3000import './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;
参考文章:面试官:前端跨页面通信,你知道哪些方法? - 掘金