HTML5 API 多端通信桥 MessageChannel 技术

这个特别有意思,可以将其理解为通信桥的概念,桥有两个端(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出现之前,你可能需要这样做:

  1. Worker A向主线程发送一个消息。

  2. 主线程接收到这个消息后,再将它转发给Worker B。

  3. Worker B接收到这个消息后,再向主线程发送一个回复。

  4. 主线程接收到这个回复后,再将它转发给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', ...)

猜你喜欢

转载自blog.csdn.net/wangsenling/article/details/132022813