最近在 GitHub 上”学习”的时候发现了别人在用 MessageChannel,这个 Api 很少见(没见过)基本不了解如何使用,搜了下 发现 React 和 Vue 都使用过~ 这不学习学习跟同事小装一手?
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的消息: pong
};
port2.onmessage = function (event) {
console.log('收到来自port1的消息:', event.data); // 收到来自port1的消息: ping
port2.postMessage('pong');
};
port1.postMessage('ping');
use addEventListener
const { port1, port2 } = new MessageChannel();
port1.addEventListener('message', function (event) {
console.log('收到来自port2的消息:', event.data); // 收到来自port2的消息: pong
});
port1.start();
port2.addEventListener('message', function (event) {
console.log('收到来自port1的消息:', event.data); // 收到来自port1的消息: ping
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 = () => {
// message channel
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























