Contents
What is MessageChannel
MessageChannel allows two different scripts running in different browser contexts of the same document (such as two iframes, the document body and an iframe, using SharedWorker
two documents, or two workers) to communicate directly, using a port on each end (port) delivers messages to each other through a two-way channel (channel).
MessageChannel
DOM Event
sends messages in the form of , so it is an asynchronous macro task.
Basic usage
- Use
MessageChannel()
the constructor to create a communication channel and obtain two port MessagePort objectsport1
port2
; - One port is used to
postMessage
send messages, and the other port is used toonmessage
receive messages; - Another port
onmessage
receives messages via; - Handling is used when the port receives a message that cannot be deserialized
onmessageerror
; - When stopping sending messages, call
close
close port;
method one
const { port1, port2 } = new MessageChannel(); port1.onmessage = ( event ) => { console.log ( ' Received message from port2:' , event.data ) ; }; port1.onmessageerror = (event) => {}; port2.onmessage = function ( event ) { console.log ( ' Message received from port1:' , event.data ) ; port2. postMessage ( 'I am port2' ); }; port2.onmessageerror = (event) => {}; port1. postMessage ( 'I am port1' );
Method 2
const { port1, port2 } = new MessageChannel(); port1. addEventListener ( 'message' , event => { console . log ( 'Message received from port2: ' , event. data ); }); port1.addEventListener('messageerror', (event) => { }); port1.start(); port2. addEventListener ( 'message' , event => { console . log ( 'Received message from port1: ' , event. data ); port2. postMessage ( 'I am port2' ); }); port2.addEventListener('messageerror', (event) => { }); port2.start(); port1. postMessage ( 'I am port1' );
The output of the above two methods is:
Received message from port1: I am port1
Received message from port2: I am port2
- In terms of usage
addEventListener
, you need to manually callstart()
the method for messages to flow, because it is paused during initialization.onmessage
The method has been called implicitlystart()
.
Execution order in Event Loop
Synchronous 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 // Microtask is executed first requestAnimationFrame setTimeout // Macro task, define it first and execute it first MessageChannel // Macro task, define it and execute it later
requestAnimationFrame – a task that is not a macrotask
window.requestAnimationFrame() tells the browser that you want to perform an animation and requires the browser to call the specified callback function to update the animation before the next redraw. This method requires passing in a callback function as a parameter, which will be executed before the next redraw of the browser — MDN
Strictly speaking, raf is not a macro task because
- The execution timing is completely inconsistent with the macro task;
- When the raf task queue is executed, all tasks in the queue at this moment will be executed;
scenes to be used
1: Context 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
Use it in most scenes that require deep copying JSON.parse(JSON.stringify(object))
. But this method will ignore undefined, function, symbol and circular reference objects .
// Deep copy function function deepClone ( val ) { return new Promise ( resolve => { const { port1, port2 } = new MessageChannel () port2.onmessage = e => resolve(e.data) port1.postMessage(val) }) }
Deep copy implemented using MessageChannel can only solve the problems of undefined and circular reference objects, but it is still helpless for Symbol and function.
practice
Problem description :
There are two methods called by a third party in an uncertain order. After both methods are called, further processing will be performed.
The solution
uses setTimeout to simulate the calling sequence of methods. 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)