MessageChannel 消息通道
一、初识 MessageChannel 对象
通过构造函数 MessageChannel() 可以创建一个消息通道,实例化的对象会继承两个属性:port1 和 port2
port1 和 port2 都是 MessagePort 对象,在这里是只读的,无法对其进行字面量赋值
不过可以给 port 添加属性
上图还体现了 MessagePort 对象具有 onmessage 和 onmessageerror 两个属性
这是两个回调方法,使用 MessagePort.postMessage 方法发送消息的时候,就会触发另一个端口的 onmessage
消息通道就像是一条左右贯通的管道,左右两个端口就是 port1 和 port2
这两个端口可以相互发送消息,port1 发送的消息可以在 port2 接收到,反之亦然
二、多个 Web Worker 之间通信
MessageChannel 可以结合 Web Worker 实现多线程通信
// main.js
let worker1 = new Worker('./worker1.js');
let worker2 = new Worker('./worker2.js');
let ms = new MessageChannel();
// 把 port1 分配给 worker1
worker1.postMessage('main', [ms.port1]);
// 把 port2 分配给 worker2
worker2.postMessage('main', [ms.port2]);
worker2.onmessage = function(event) {
console.log(event.data);
}
这里的 postMessage() 可以接收两个参数:message、transferList
message | 消息内容,可以是任意基础数据类型 |
transferList | 由被传输对象组成的数组,这些对象的所有权会转移给调用 postMessage 的对象 |
所以上面的代码,就是把消息通道的 port1 分配给了 worker1,把 port2 分配给 worker2
也就是用消息通道,将两个 worker 给连接起来
// worker1.js
onmessage = function(e) {
if (e.data === 'main') {
const port = e.ports[0];
port.postMessage('Hi! I'm worker1');
}
}
// worker2.js
onmessage = function(e) {
if (e.data === 'main') {
const port = e.ports[0];
port.onmessage = function(e) {
postMessage(e.data);
}
}
}
代码运行的时候,worker1 中通过 port1 发送消息,然后 worker2 就能从 port2 中接收到消息
三、深拷贝
大部分需要深拷贝的场景,都可以使用以下代码:
JSON.parse(JSON.stringify(object))
但这种办法会忽略 undefined、function、symbol 和循环引用的对象
而通过 postMessage() 方法传输的 message 参数是深拷贝的
所以可以借用 MessageChannel 实现深拷贝:
// 深拷贝函数
function deepClone(val) {
return new Promise(resolve => {
const { port1, port2 } = new MessageChannel()
port2.onmessage = e => resolve(e.data)
port1.postMessage(val)
})
}
// 定义一个包含 undefined 的对象
let obj = {
a: 'wise',
b: undefined,
c: {
d: 'wrong'
}
}
// 循环引用
obj.c.e = obj.c
// 注意该方法是异步的
async function test() {
const clone = await deepClone(obj)
console.log(clone)
}
test()
但这个深拷贝只能解决 undefined 和循环引用对象的问题,对于 Symbol 和 function 依然束手无策
参考资料: