MessageChannel 消息通道, JS 实现消息传递及API功能扩展

233 阅读2分钟

概述

Channel Messaging API 的MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据。

继承自父类 EventTarget

方法

MessagePort.postMessage

从端口发送一条消息,并且可选是否将对象的所有权交给其他浏览器上下文

MessagePort.start

开始发送该端口中的消息队列 (只有使用 EventTarget.addEventListener 的时候才需要调用;当使用 MessagePort.onmessage 时,是默认开始的。)

MessagePort.close

断开端口连接,它将不再是激活状态。

事件回调

MessagePort.onmessage

是一个 EventListener, 当类型为 messageMessageEvent 在该端口触发时,它将会被调用 ── 也就是说,该端口收到了一条消息。

onmessageerror

是一个 EventListener, 当类型为 MessageErrorMessageEvent 被触发时,它将会被调用 ── 这意味着,端口收到了一条无法被反序列化的消息。

基本用法

const { port1, port2 } = new MessageChannel();
port1.onmessage = function (event) {
  console.log('收到来自port2的消息:', event.data); // 收到来自port2的消息: pong
};

// port1.addEventListener('message', function (event) {
//   console.log('收到来自port2的消息:', event.data); // 收到来自port2的消息: pong
// });
// port1.start();

port2.onmessage = function (event) {
  console.log('收到来自port1的消息:', event.data); // 收到来自port1的消息: ping
  port2.postMessage('pong');
};
port1.postMessage('ping');
  • 注意: addEventListener之后要手动调用start()方法消息才能流动,因为初始化的时候是暂停的。onmessage已经隐式调用了start()方法。

常用场景

用作深拷贝

function deepClone(obj) {
  return new Promise((resolve, reject) => {
    try {
      const { port1, port2 } = new MessageChannel();
      port2.onmessage = e => resolve(e.data);
      port1.postMessage(obj);
    } catch (e) {
      reject(e);
    }
  });
}
const oldObj = { a: { b: 1 } };
deepClone(oldObj).then((newObj) => {
  console.log(oldObj === newObj); // false
  newObj.a.b = 2;
  console.log(oldObj.a.b); // 1
}).catch(e => {
    console.log(e); // 序列化错误
});
  • 当消息包含函数Symbol等不可序列化的值时,就会报无法克隆的DOM异常

Web Worker(worker间通信)

// worker1.js
self.onmessage = function(e) {
    const  port = e.ports[0];
    port.postMessage("this is from worker1")        
}

// worker2.js
self.onmessage = function(e) {
    const port = e.ports[0];
    port.onmessage = function(e) {
        self.postMessage(e.data)
    }
}

// index.js
const worker1 = new Worker("worker1.js");
const worker2 = new Worker("worker2.js");
const channel = new MessageChannel();
worker1.postMessage("port1", [channel.port1]);
worker2.postMessage("port2", [channel.port2]);
worker2.onmessage = function(e) {
    console.log(e.data);
}

iframe 和 页面之间通信

  • 先使用 window.postMessage 建立 "连接"
  • 之后的操作参考 worker间通信

...(持续补充)

参考文档

developer.mozilla.org/zh-CN/docs/…

www.jianshu.com/p/4f07ef18b…

zhuanlan.zhihu.com/p/432726048