乐趣区

关于javascript:浅谈-MessageChannel

什么是 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 // 微工作先执行
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 仍然大刀阔斧。

退出移动版