什么是 MessageChannel

MessageChannel 容许两个不同的脚本运行在同一个文档的不同浏览器上下文(例如两个 iframe,文档主体和一个 iframe,应用 SharedWorker 的两个文档,或两个 worker)来间接通信,在每端应用一个端口(port)通过双向频道(channel)向彼此传递音讯。

MessageChannel 是以DOM Event的模式发送音讯,所以它属于异步的宏工作。

根本用法

  1. 应用 MessageChannel() 构造函数来创立通信信道,获取两个端口 MessagePort 对象 port1 port2
  2. 一个端口应用 postMessage发送音讯,另一个端口通过 onmessage 接管音讯;
  3. 另一个端口通过 onmessage 接管音讯;
  4. 当端口收到无奈反序列化的音讯时,应用 onmessageerror解决;
  5. 进行发送音讯时,调用 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 // 微工作先执行requestAnimationFramesetTimeout // 宏工作,先定义先执行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 仍然大刀阔斧。