最近在 GitHub 上”学习”的时候发现了别人在用 MessageChannel,这个 Api 很少见(没见过)基本不了解如何使用,搜了下 发现 React 和 Vue 都使用过~ 这不学习学习跟同事小装一手?
介绍 Message Channel
Message Channel
允许两个不同的脚本运行在同一个文档的不同浏览器上下文(例如两个 Iframe,文档主体和 Iframe,worker 之间或 window.open 的窗口)来直接通讯,在每一端使用一个端口(port)通过双向频道(Channel)来传递信息。
Message Channel 是以 Dom Event
的形式发送信息,属于异步的宏任务
。
使用
- 使用
MessageChannel()
构造函数来创建通讯信息,获取两个端口的 Message Port 对象:Port1、Port2
- 端口之间可以互相通信
- 端口接受到无法被序列化的消息时,使用
onmessageerror
处理
- 使用
close
关闭端口
示例
const { port1, port2 } = new MessageChannel(); port1.onmessage = function (event) { console.log('收到来自port2的消息:', event.data); }; port2.onmessage = function (event) { console.log('收到来自port1的消息:', event.data); port2.postMessage('pong'); }; port1.postMessage('ping');
|
use addEventListener
const { port1, port2 } = new MessageChannel(); port1.addEventListener('message', function (event) { console.log('收到来自port2的消息:', event.data); }); port1.start(); port2.addEventListener('message', function (event) { console.log('收到来自port1的消息:', event.data); port2.postMessage('pong'); }); port2.start(); port1.postMessage('ping');
|
使用 EventListener
需要手动调用 start()
后才可通信,因为初始化是暂停的。在使用 onmessage
时隐式调用了 start()
方法。
与 Iframe 通信
Main
const channel = new MessageChannel(); const { port1, port2 } = channel;
port1.onmessage = (event) => { console.log(event.data, '----- port1 get message'); };
port2.onmessage = (event) => { console.log(event.data, '------ port2 get message'); };
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', () => { iframe.contentWindow.postMessage('from main -- postMessage', '*', [port1]); });
|
Iframe
window.addEventListener('message', (event) => { const { data, ports } = event; const [port1] = ports || []; console.log(data, 'iframe get message', event); });
|
互相通信
Iframe
+ let messagePort; window.addEventListener('message', (event) => { const { data, ports } = event; const [port1] = ports || []; console.log(data, 'iframe get message', event); + messagePort = port1; });
+button.onclick = () => { // message channel + messagePort?.postMessage('message from iframe') +}
|
此处的 port1
是来自:iframe.contentWindow.postMessage('xxxx', '*', [port1]);
( postMessage 方法能够接受一个由 Transferable Objects 组成的数组作为参数,而 MessageChannel 导出的 MessagePort 刚好是 Transferable Objects )
Window.open
Main
let strWindowFeatures = 'top=10,left=10,width=400,height=200'; const newWindow = window.open(url, 'message', strWindowFeatures);
newWindow.addEventListener('load', () => { newWindow.postMessage('message from new window', '*', [port1]); });
|
new window
与 Iframe 逻辑大体一致
let messagePort; window.addEventListener('message', (event) => { const { data, ports } = event; const [port1] = ports || []; console.log(data, 'iframe get message', event); messagePort = port1; });
button.onclick = () => { messagePort?.postMessage('message from iframe'); };
|
Event Loop 中执行顺序
const interval = setInterval(() => { console.log('setInterval'); window.clearInterval(interval); });
setTimeout(() => { console.log('timeout'); });
const { port1, port2 } = new MessageChannel(); port1.postMessage('send message'); port2.onmessage = () => { console.log('get message'); };
requestAnimationFrame(() => console.log('requestAnimationFrame'));
new Promise((resolve) => { console.log('promise'); resolve(); }).then(() => { console.log('then'); });
async function A() { console.log('A run'); await new Promise((resolve) => resolve()); console.log('A end'); }
A();
|
输出为
promise, A run then, A end requestAnimationFrame timeout(先调先运行), get message, setInterval
|
https://segmentfault.com/a/1190000042501046
https://zhuanlan.zhihu.com/p/432726048
https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel/MessageChannel