这个特别有意思,可以将其理解为通信桥的概念,桥有两个端(port1,port2)只要将port1,port2指定到任意两个进程,无论是iframe-iframe,iframe-worker,parent-child-iframe,worker-worker等,只要搭好,两者就可以实时通信了。这解决了让parent作为中转站这种头大的问题,以下是该技术调研的细节。
相关链接:MessageChannel - Web API 接口参考 | MDN
在MessageChannel出现之前,跨上下文(例如,主线程、Web Workers、Service Workers或者不同的窗口或iframe)通信主要依赖于postMessage和onmessage事件。这种方式虽然有效,但在某些情况下可能会比较麻烦。
例如,假设你有一个主线程和两个Web Workers,你希望这两个Web Workers能够直接通信。在MessageChannel出现之前,你可能需要这样做:
-
Worker A向主线程发送一个消息。
-
主线程接收到这个消息后,再将它转发给Worker B。
-
Worker B接收到这个消息后,再向主线程发送一个回复。
-
主线程接收到这个回复后,再将它转发给Worker A。
这种方式需要主线程作为中介,进行大量的消息转发,这可能会增加主线程的负担,降低应用程序的性能。
而有了MessageChannel之后,你可以直接在两个Web Workers之间创建一个通信通道,然后这两个Web Workers就可以直接通信,无需通过主线程。这样可以减少主线程的负担,提高应用程序的性能。
此外,MessageChannel还支持传输Transferable对象,这可以避免数据的复制,进一步提高性能。而在MessageChannel出现之前,如果你想在不同的上下文之间传输大量的数据,你可能需要进行昂贵的数据复制或者序列化和反序列化操作。
总的来说,MessageChannel提供了一种更简单、更直接、更高效的跨上下文通信方式。
HTML5 中案例代码
https://github.com/mdn/dom-examples/tree/main/channel-messaging-basic
下面是我根据 ChatGPT 探索的两个 iframe 的双向通信代码,记住 iframe 和 webview 一样,在没将页面 load 完成时,你发过去的消息,嵌入页面收不到,这个在测试时是不报错的,很恶心!
index.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width" />
<title>Channel messaging demo</title>
</head>
<body>
<h1>Channel messaging demo</h1>
<p class="output">My body</p>
<iframe id="iframe1" src="page1.html" width="480" height="320"></iframe>
<iframe id="iframe2" src="page2.html" width="480" height="320"></iframe>
<script>
const iframe1 = document.getElementById('iframe1');
const iframe2 = document.getElementById('iframe2');
const channel = new MessageChannel();
iframe1.onload = () => {
iframe1.contentWindow.postMessage('port', '*', [channel.port1]);
}
iframe2.onload = () => {
iframe2.contentWindow.postMessage('port', '*', [channel.port2]);
}
</script>
</body>
</html>
Page1.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width"/>
<title>My page title</title>
<STYLE>
body {
background-color: antiquewhite;
}
.output {
color: #747070;
}
</STYLE>
</head>
<body>
<p class="output">iFrame body</p>
<script>
const output = document.querySelector(".output");
window.onmessage = (event) => {
const port = event.ports[0];
console.log(`Page1初始拿到的数据${event.data}`);
port.onmessage = (e) => {
// console.log('来自iframe2的消息:', e.data);
output.innerHTML = e.data;
};
port.postMessage('Hello, iframe2!');
let timer = 1;
setInterval(() => {
port.postMessage(`这是来自iframe1第${timer}消息`);
timer++;
}, 2000)
};
</script>
</body>
</html>
Page2.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width"/>
<title>My page title</title>
</head>
<body>
<p class="output">iFrame body</p>
<script>
const output = document.querySelector(".output");
window.onmessage = (event) => {
const port = event.ports[0];
console.log(`Page2初始拿到的数据${event.data}`);
port.onmessage = (e) => {
// console.log('来自iframe1的消息:', e.data);
output.innerHTML = e.data;
};
port.postMessage('Hello, iframe1!');
let timer = 1;
setInterval(() => {
port.postMessage(`这是来自iframe2第${timer}消息`);
timer++;
}, 1000)
};
</script>
</body>
</html>
三者之间的实现效果
补充知识
postMessage 的三个参数分别有什么作用?
window.postMessage(message, targetOrigin, [transfer])
targetOrigin参数是一个字符串,表示消息的目标源(协议+主机+端口)。你可以使用这个参数来限制哪些窗口可以接收你的消息,以提高安全性。
以下是使用targetOrigin参数的示例:
htmlCopy code<!-- 父页面 -->
<iframe id="iframe" src="http://example.com/iframe.html"></iframe>
<script>
const iframe = document.getElementById('iframe');
iframe.onload = () => {
// 只有当iframe的源是http://example.com时,它才能接收这个消息
iframe.contentWindow.postMessage('Hello, world!', 'http://example.com');
};
</script>
当 targetOrigin="*"时,则代表着向所有 iframe 进行广播的效果
window.onmessage 和 window.addEventListener('message', ...)两者之间的区别是什么?
window.onmessage和window.addEventListener('message', ...)都是用于监听message事件的方法,但它们的使用方式和行为有一些不同。
window.onmessage:这是一个事件处理属性,你可以将它设置为一个处理函数,这个函数会在message事件发生时被调用。如果你多次设置window.onmessage,那么后面的设置会覆盖前面的设置。也就是说,你只能有一个onmessage处理函数。
javascriptCopy codewindow.onmessage = (event) => {
console.log(event.data);
};
window.addEventListener('message', ...):这是一个方法,你可以用它来添加一个或多个message事件的监听器。如果你多次调用addEventListener,那么每次调用都会添加一个新的监听器,而不会覆盖旧的监听器。也就是说,你可以有多个message事件的监听器。
javascriptCopy codewindow.addEventListener('message', (event) => {
console.log(event.data);
});
总的来说,如果你只需要一个message事件的处理函数,你可以使用window.onmessage。如果你需要多个message事件的处理函数,或者你需要更复杂的事件处理(例如,使用捕获阶段,或者移除事件监听器),你可以使用window.addEventListener('message', ...)