在iframe嵌套的业务中,如果父页面需要向子页面发送消息,通过传统的iframe.contentWindow.postMessage和window.addEventListener('message',(data) => {})的方式,有时候会出现父页面发送了消息,但是子页面没有收到消息的情况,这一般是由于子页面加载并创建message监听的时机出现了延误。在这里我们通过发布订阅的方案来解决它。
发布订阅的clsss实现
jsexport class PubSub{
constructor(){
this.subscribers = {}; // 存储订阅者
}
// 订阅
subscribe(event,callback){
if(!this.subscribers[event]){
this.subscribers[event] = []
}
this.subscribers[event].push(callback)
return () => {
this.unsubscribe(event,callback)
}
}
unsubscribe(event, callback) {
if (!this.subscribers[event]) return;
const index = this.subscribers[event].indexOf(callback);
if (index > -1) {
this.subscribers[event].splice(index, 1);
}
}
// 发布消息
publish(event, ...args) {
if (!this.subscribers[event]) return;
this.subscribers[event].forEach(fn => {
fn.apply(this, args);
});
}
}
export class AdvancedPubSub extends PubSub{
constructor(){
super();
this.cache = {} // 缓存已发布的消息
}
// 重写发布方法,支持缓存
publish(event, ...args) {
// 缓存消息
if (!this.cache[event]) {
this.cache[event] = [];
}
this.cache[event].push(args);
// 正常通知订阅者
super.publish(event, ...args);
}
// 重写订阅方法,支持先发布再订阅
subscribe(event, callback) {
// 如果有缓存的消息,立即执行
if (this.cache[event]) {
this.cache[event].forEach(args => {
callback.apply(this, args);
});
}
// 正常订阅
return super.subscribe(event, callback);
}
}
通过es6 Proxy的方式将一个class包装成单例
jsexport function singletonWrap (className) {
let ins
const proxy = new Proxy(className, {
construct (target, args) {
if (!ins) {
ins = new className(...args)
} else {
console.warn(`${className}只有一个实例`);
}
return ins
}
})
// 修改原型链,防止通过构造函数创建新的实例
className.prototype.constructor = proxy
return proxy
}
html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./index.js" type="module"></script>
<!--
index.js内容
import {singletonWrap} from "./singletonWrap.js"
import {AdvancedPubSub} from "./eventBus.js"
const ps = singletonWrap(AdvancedPubSub)
window.PUBSUB = new ps()
-->
<style>
#iframe-container {
width: 100%;
margin-top: 20px;
}
</style>
</head>
<body>
<div>
<button onclick="send()">
发送
</button>
</div>
<div id="iframe-container">
<!-- <iframe src="./child.html" width="600" height="400" id="iframe"></iframe> -->
</div>
<script>
function send() {
const date = new Date().toLocaleString()
console.log("当前时间: ", date)
console.log("PUBSUB: ", this.PUBSUB)
this.PUBSUB.publish("test01",{
data: date
})
setTimeout(() => {
let iframe = document.getElementById("iframe")
if (!iframe) {
const iframeContainer = document.getElementById('iframe-container');
iframe = document.createElement('iframe');
iframe.src = './child.html';
iframe.width = '500';
iframe.height = '400';
iframe.id = "iframe"
iframeContainer.appendChild(iframe);
}
}, 3000);
}
</script>
</body>
</html>
html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
padding: 0;
margin: 0;
}
html,body{
width: 100%;
height: 100%;
}
#container{
height: 100%;
width: 100%;
background-color: pink;
}
</style>
</head>
<body>
<div id="container">
我是child iframe
</div>
<script>
window.onload = () => {
console.log("child iframe onload事件触发")
this.PUBSUB = window.parent.PUBSUB
this.PUBSUB.subscribe("test01",(e) => {
const container = document.getElementById("container")
container.innerText += `\n ${e.data}`
})
}
</script>
</body>
</html>
AdvancedPubSub可以实现先发布再订阅的功能,这样我们在子页面就不会造成由于加载延迟造成之前的消息无法消费的问题


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!