什么是 MessageChannel
MessageChannel 容许两个不同的脚本运行在同一个文档的不同浏览器上下文(例如两个 iframe,文档主体和一个 iframe,应用 SharedWorker
的两个文档,或两个 worker)来间接通信,在每端应用一个端口(port)通过双向频道(channel)向彼此传递音讯。
MessageChannel 是以
DOM Event
的模式发送音讯,所以它属于异步的宏工作。
根本用法
- 应用
MessageChannel()
构造函数来创立通信信道,获取两个端口 MessagePort 对象port1
port2
; - 一个端口应用
postMessage
发送音讯,另一个端口通过onmessage
接管音讯; - 另一个端口通过
onmessage
接管音讯; - 当端口收到无奈反序列化的音讯时,应用
onmessageerror
解决; - 进行发送音讯时,调用
close
敞开端口;
形式一
const {port1, port2} = new MessageChannel();
port1.onmessage = (event) => {console.log('收到来自 port2 的音讯:', event.data);
};
port1.onmessageerror = (event) => {};
port2.onmessage = function (event) {console.log('收到来自 port1 的音讯:', event.data);
port2.postMessage('我是 port2');
};
port2.onmessageerror = (event) => {};
port1.postMessage('我是 port1');
形式二
const {port1, port2} = new MessageChannel();
port1.addEventListener('message', event => {console.log('收到来自 port2 的音讯:', event.data);
});
port1.addEventListener('messageerror', (event) => {});
port1.start();
port2.addEventListener('message', event => {console.log('收到来自 port1 的音讯:', event.data);
port2.postMessage('我是 port2');
});
port2.addEventListener('messageerror', (event) => {});
port2.start();
port1.postMessage('我是 port1');
以上两种形式,输入均为:
收到来自 port1 的音讯:我是 port1
收到来自 port2 的音讯:我是 port2
- 应用
addEventListener
形式,须要手动调用start()
办法音讯能力流动,因为初始化的时候是暂停的。onmessage
曾经隐式调用了start()
办法。
Event Loop 中的执行程序
同步工作 > 微工作 > requestAnimationFrame > DOM 渲染 > 宏工作
setTimeout(() => {console.log('setTimeout')
}, 0)
const {port1, port2} = new MessageChannel()
port2.onmessage = e => {console.log(e.data)
}
port1.postMessage('MessageChannel')
requestAnimationFrame(() => {console.log('requestAnimationFrame')
})
Promise.resolve().then(() => {console.log('Promise1')
})
输入为:
Promise // 微工作先执行
requestAnimationFrame
setTimeout // 宏工作,先定义先执行
MessageChannel // 宏工作,后定义后执行
requestAnimationFrame – 不是宏工作的工作
window.requestAnimationFrame() 通知浏览器——你心愿执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该办法须要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行 — MDN
严格意义上来说,raf 并不是一个宏工作,因为
- 执行机会和宏工作齐全不统一;
- raf 工作队列被执行的时候,会将其此刻队列中所有的工作都执行完;
应用场景
一:同一个 document 的上下文通信
var channel = new MessageChannel();
var para = document.querySelector('p');
var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;
ifr.addEventListener("load", iframeLoaded, false);
function iframeLoaded() {otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}
channel.port1.onmessage = handleMessage;
function handleMessage(e) {para.innerHTML = e.data;}
二:联合 Web Worker 实现多线程通信
三:深拷贝
大部分须要深拷贝的场景,都应用 JSON.parse(JSON.stringify(object))
。但这种方法会疏忽 undefined、function、symbol 和 循环援用的对象。
// 深拷贝函数
function deepClone(val) {
return new Promise(resolve => {const { port1, port2} = new MessageChannel()
port2.onmessage = e => resolve(e.data)
port1.postMessage(val)
})
}
应用 MessageChannel 实现的深拷贝只能解决 undefined 和 循环援用对象的问题,对于 Symbol 和 function 仍然大刀阔斧。