MessageChannel:JavaScript中实现高效深度复制的秘密武器

6,821 阅读3分钟

MessageChannel

MessageChannel 是一种用于在不同的浏览上下文之间传递数据的机制。它可将数据在两个不同的窗口、Iframe 或 web worker 之间传递。过去,我们在 web 应用中通常使用的数据传递方式是 postMessagelocalStorage,它们同样也是用于在不同浏览上下文之间传递数据的方式。但与它们不同的是,MessageChannel 支持一些高级特性:

  • 多路传输:一个 MessageChannel 实例可同时传输多个消息;
  • 双向传输:MessageChannel 双向传输,即可同时在两个端口之间发送和接收消息;
  • 接收发送的消息不需要特定的数据结构;
  • 以及传输更大的数据。

基本用法

创建 MessageChannel 实例

可以通过 MessageChannel 构造函数来创建一个 MessageChannel 实例:

const channel = new MessageChannel();

发送数据

要发送数据,可以使用 MessageChannel 实例的 port1 属性来向 channel 的另一端发送数据:

const message = { data: "Hello MessageChannel!" };
channel.port1.postMessage(message);

接收数据

要接收数据,可以使用 MessageChannel 实例的 port2 属性来监听 message 事件。当有数据到来时,message 事件的回调函数被调用,我们可以在函数中处理收到的数据:

channel.port2.onmessage = function(event) {
  console.log(event.data); // 输出 "Hello MessageChannel!"
};

关闭 MessageChannel

在传输完成后应该手动关闭 MessageChannel 以释放资源:

channel.port1.close();
channel.port2.close();

注意:在 web worker 之间传递 MessageChannel 时,仅关闭 port1 不会关闭 port2,反之亦然。

应用场景

MessageChannel 的应用场景十分广泛,下面介绍一些常见的应用场景。

多窗口/ IFrame 之间的通信

在同一页面下的不同 iframe 之间进行通信一直是前端工程师们需要面对的问题,这时我们就可以使用 MessageChannel 来进行多个浏览上下文之间的通信。假设有两个 iframe,它们的父级 window 对象的引用分别为 parent,而在两个 iframe 的脚本中分别创建了 MessageChannel 实例,并使用 port2 监听 message 事件:

// 在 iframe1 中
const iframe1 = document.querySelector("#iframe1");
  
const channel1 = new MessageChannel();
iframe1.contentWindow.postMessage({type: "connect"}, '*', [channel1.port1]);

channel1.port2.onmessage = function(evt) {
  console.log(evt.data);
}
// 在 iframe2 中
const iframe2 = document.querySelector("#iframe2");
  
const channel2 = new MessageChannel();
iframe2.contentWindow.postMessage({type: "connect"}, '*', [channel2.port1]);

channel2.port2.onmessage = function(evt) {
  console.log(evt.data);
}

这样,使用 MessageChannel 便可以使两个 iframe 之间相互传递消息。

主线程和 web worker 之间的通信

在 web worker 中计算复杂算法,并将结果发送回主线程,可以减小主线程的负担,从而提高页面响应时间。使用 MessageChannel,这一过程可以变得相对容易。

在主线程中创建 MessageChannel 实例,将其中一个端口发送给 Web Worker:

const worker = new Worker('yourworker.js');
const channel = new MessageChannel();
  
worker.postMessage({type: "connect"}, [channel.port2]);
  
channel.port1.onmessage = function(evt) {
  console.log(evt.data);
}

在 Web Worker 中创建与发送端口相匹配的 onmessage 处理程序,以便在主线程发送消息时,可以接收结果:

self.onmessage = function(evt) {
  const channel = new MessageChannel();
  
  evt.ports[0].postMessage({data: 'response'}, [channel.port2]);
  
  channel.port1.onmessage = function(evt) {
    console.log(evt.data);
  }
};

深拷贝对象(骚操作)

在前端开发的时候拷贝对象是最常见的操作,通常对简单的对象进行拷贝,可以使用 JSON.stringify和JSON.parse可以完成,但是如果碰到多层嵌套对象,此方法就无效;下面将使用MessageChannel就能实现一个代码超级少的深拷贝:

const deepClone =(obj) => {
   return new Promise((resolve,reject)=>{
      const {port1,port2} = new MessageChannel();
      port1.postMessage(obj)
      port2.onmessage=(e)=>{
          resolve(e.data)
            //port1.close()
            //port2.close()
      }
     
   })
};
const obj={a:1,b:{
    c:1,
    d:{
        f:1,
        g:1
    }
}}
deepClone(obj).then((obj2)=>{
    console.log(obj2)
    obj.b.d.f=2
    console.log(obj,obj2)
})

总结

以上就是 MessageChannel 的基础用法和一些应用场景的介绍。它能提高多个浏览上下文之间的通信效率,也方便了主线程和 Web Worker 之间的消息传递。需要注意的是,在使用 MessageChannel 时,要及时关闭通道,以释放资源。