2

What is MessageChannel

MessageChannel allows two different scripts running in different browser contexts of the same document (e.g. two iframes, a document body and an iframe, two documents using SharedWorker , or two workers) to communicate directly , using a port at each end to pass messages to each other over a bidirectional channel.

MessageChannel sends messages in the form of DOM Event , so it is an asynchronous macro task.

Basic usage

  1. Use the MessageChannel() constructor to create a communication channel and get two port MessagePort objects port1 port2 ;
  2. One port uses postMessage to send messages, and the other port uses onmessage to receive messages;
  3. Another port receives messages via onmessage ;
  4. When the port receives a message that cannot be deserialized, use onmessageerror to process;
  5. When you stop sending messages, call close to close the port;

method one

 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');

Method 2

 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');

In the above two ways, the output is:

Received message from port1: I am port1
Received message from port2: I am port2

  • Using the addEventListener method, you need to manually call start() method message to flow, because the initialization is suspended.
  • onmessage has implicitly called the start() method.

Execution order in Event Loop

Sync Tasks > Micro Tasks > requestAnimationFrame > DOM Rendering > Macro Tasks

 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')
})

The output is:

 Promise // 微任务先执行
requestAnimationFrame
setTimeout // 宏任务,先定义先执行
MessageChannel // 宏任务,后定义后执行

requestAnimationFrame - a task that is not a macro task

window.requestAnimationFrame() tells the browser that you want to perform an animation and asks the browser to call the specified callback function to update the animation before the next repaint. This method needs to pass in a callback function as a parameter, the callback function will be executed before the browser's next repaint --- MDN

Strictly speaking, raf is not a macro task because

  • The execution timing and macro tasks are completely inconsistent;
  • When the raf task queue is executed, all tasks in the queue will be executed;

scenes to be used

One: contextual communication of the same 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;
}

Two: Combined with Web Worker to achieve multi-threaded communication

Three: deep copy

Most scenarios that require deep copying use JSON.parse(JSON.stringify(object)) . But this approach ignores undefined, function, symbol and circularly referenced objects .

 // 深拷贝函数
function deepClone(val) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = e => resolve(e.data)
    port1.postMessage(val)
  })
}

The deep copy implemented by MessageChannel can only solve the problem of undefined and circular reference objects, and it is still helpless for Symbol and function.

practice

Problem description :
There are two methods called by a third party in an indeterminate order. After both methods are called, further processing is performed.

Solution <br>Use setTimeout to simulate the calling sequence of the method, the code is as follows:

 const { port1, port2 } = new MessageChannel()
let data

const handleUser = newData => {
  if (data) {
    const result = { ...data, ...newData }
    console.log('获取到全部数据', result)
    port1.close()
    port2.close()
  } else {
    data = newData
  }
}

const getName = () => {
  const params = { name: '123' }
  port1.postMessage(params)
  port1.onmessage = e => {
    handleUser(e.data)
  }
}
const getAge = () => {
  const params = { age: 88 }
  port2.postMessage(params)
  port2.onmessage = e => {
    handleUser(e.data)
  }
}

setTimeout(() => {
  getName()
}, 0)

setTimeout(() => {
  getAge()
}, 10)

时倾
794 声望2.4k 粉丝

把梦想放在心中