js MessageChannel通道传输

window.MessageChannel也是浏览器提供的一个异步操作的API,属于宏任务

应用场景

  • 不同浏览器上下文通信。比如window.open()打开的窗口或者iframe等之间建立通信管道,并通过两端的端口(port1和port2)发送消息。因此实现专用信道通信,而不必对非指定窗口进行过滤。
  • worker跨线程通信
  • 对象深拷贝,利用消息在发送和接收的过程需要序列化和反序列化。

这个api会创建一个管道,管道的两端分别代表一个messagePort,都能够通过portMessage向对方发送数据,通过onmessage来接受对方发送过来的数据
在这里插入图片描述
基本使用

const ch = new MessageChannel()
const port1 = ch.port1
const port2 = ch.port2

port1.onmessage = function(d) {
    console.log(`port1接收的消息是:${d.data}`)
}

port2.onmessage = function(d) {
    console.log(`port2接收的消息是:${d.data}`)
}

//addEventListener写法也可以,但必须手动调用start,onmessage写法隐式调用了start
port2.addEventListener('message', function (event) {
	...
});
//addEventListener写法
port1.start();
//addEventListener写法
port2.start();

// 发送消息
port1.portMessage('port1发送的消息')
port2.portMessage('port2发送的消息')

port1.close() //关闭连接
port1.onmessageerror=fn; //消息不能反序列化时,会出现错误,这时可以用onmessageerror方法捕获

应用场景
让两个web worker可以通过MessageChannel通信,即实现多线程通信

  • 注意,此时port在主线程不可用了,因为postMessage第二个参数是transferable的原因,让线程中的变量内存被转移到了另一个线程
// index.html
<script>
    var w1 = new Worker("worker1.js");
    var w2 = new Worker("worker2.js");
    var ch = new MessageChannel();
    w1.postMessage("port1", [ch.port1]); //第二个参数不会被编码转换导致失效
    w2.postMessage("port2", [ch.port2]);
    w2.onmessage = function(e) {
        console.log(e.data);
    }
</script>
====worker1.js
// worker1.js
self.onmessage = function(e) {
  const  port = e.ports[0];
  port.postMessage("this is from worker1") //woker1中通过port1发送给port2        
}

====worker2.js
// worker2.js
self.onmessage = function(e) {
    const port = e.ports[0];
    port.onmessage = function(e) { 
        postMessage(e.data) //woker2中监听woker1中port1传递的数据,然后触发自身woker的监听函数
    }
}

对象深拷贝

  • 当消息包含函数、Symbol等不可序列化的值时,就会报无法克隆的DOM异常
function deepClone(obj) {
  return new Promise((resolve, reject) => {
    try {
      const { port1, port2 } = new MessageChannel();

      port2.onmessage = function (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
});

和iframe之间通信

<script>
    var channel = new MessageChannel();
    var output = document.querySelector('.output');
    var iframe = document.querySelector('iframe');

    // Wait for the iframe to load
    iframe.addEventListener("load", onLoad);

    function onLoad() {
        channel.port2.onmessage = onMessage;
        iframe.contentWindow.postMessage('I am from main page!', '*', [channel.port1]);
    }

    // Handle messages received on port1
    function onMessage(e) {
        console.log(e, 'main');
        output.innerHTML = e.data + '---main--';
    }

</script>

<script>
    var output = document.querySelector('.output');

    window.addEventListener('message', onMessage);

    function onMessage(e) {
        output.innerHTML = e.data;
        e.ports[0].postMessage('I am from iframe page');
    }
</script>

在vue中的应用

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(nextTickHandler)
  }
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = nextTickHandler
  timerFunc = () => {
    port.postMessage(1)
  }
} else
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    const p = Promise.resolve()
    timerFunc = () => {
      p.then(nextTickHandler)
    }
  } else {
    timerFunc = () => {
      setTimeout(nextTickHandler, 0)
    }
  }

在React中的应用

  • 由于requestIdleCallback工作帧率低,只有20FPS,还有兼容问题,React并没有使用它,而是用requestAnimationFrame和MessageChannel进行polyfill
  • 下列实现可以类似递归的方式处理剩余任务
// SchedulerHostConfig.default.js
...
const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    // Yield after `yieldInterval` ms, regardless of where we are in the vsync
    // cycle. This means there's always time remaining at the beginning of
    // the message event.
    deadline = currentTime + yieldInterval;
    const hasTimeRemaining = true;
    try {
      const hasMoreWork = scheduledHostCallback(
        hasTimeRemaining,
        currentTime,
      );
      if (!hasMoreWork) {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      } else {
        // If there's more work, schedule the next message event at the end
        // of the preceding one.
        port.postMessage(null);
      }
    } catch (error) {
      // If a scheduler task throws, exit the current browser task so the
      // error can be observed.
      port.postMessage(null);
      throw error;
    }
  } else {
    isMessageLoopRunning = false;
  }
  // Yielding to the browser will give it a chance to paint, so we can
  // reset this.
  needsPaint = false;
};

const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
...

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值