这个特别有意思,可以将其理解为通信桥的概念,桥有两个端(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', ...)