Cross document messaging, Cross worker messaging, channel messaging
MessageEvent
Message event MessageEvent() be used for:
- Cross document messaging (see Window.postMessage() And window onmessage).
- Channel messaging (see MessagePort.postMessage() (en-US) and MessagePort.onmessage).
- Cross worker / document messaging (see the above two entries, and Worker.postMessage(), Worker.onmessage, ServiceWorkerGlobalScope.onmessage (en-US) , wait.)
- Web sockets (see WebSocket onmessage attribute of the interface)
- Server-sent events (see EventSource.onmessage (en-US)).
- Broadcast channels (see Broadcastchannel.postMessage() )And BroadcastChannel.onmessage).
- WebRTC data channels (see RTCDataChannel.onmessage (en-US)).
Properties:
attribute | explain |
---|---|
data | Contains arbitrary string data, which is sent by the original script |
origin | A string containing the scheme, domain name and port of the original document (such as: http://domain.example:80) |
lastEventId | A string containing the unique identifier of the current message event. |
source | Reference to the window of the original file. Rather, it is a WindowProxy object. |
ports | An array containing any MessagePort Object to send a message. |
Cross document communication
The most common example is iframe.
otherWindow.postMessage(message, targetOrigin, [transfer]) window.addEventListener("message", (messageEvent) => {}, false)
targetOrigin: specify which windows can receive message events through the origin attribute of the window. Its value can be string "*" (indicating unlimited) or a URI. If you know exactly which window the message should be sent to, please always provide a targetOrigin with an exact value instead of *. Failure to provide an exact target will lead to data disclosure to any malicious site interested in the data.
Main page
<iframe src="iframe_1.html"></iframe> <iframe src="iframe_2.html"></iframe>
iframe_1.html
<input id="data" /> <button id="btn">send out</button> <script> let btn = document.querySelector('#btn') btn.addEventListener('click', function () { let data = document.querySelector('#data') window.parent.frames[1].postMessage(data.value, '*') return false }) </script>
iframe_2.html
Received data:<span id="message"></span> <script> let msg = document.querySelector('#message') window.addEventListener('message', (msgEvent) => { msg.innerHTML = msgEvent.data }) </script>
worker communication
A background task that can be created by a script. It can send and receive information to its creator during task execution.
worker.postMessage(aMessage, transferList) worker.onmessage = (msgEvent) => {} // ① worker.addEventListener('message', (msgEvent) => {}) // ②
Main page
const worker = new Worker('worker.js') worker.postMessage({ num1: num1.value, num2: num2.value }) worker.onmessage = function (msgEvent) { dom.innerHTML = msgEvent.data }
worker.js
onmessage = function (msgEvent) { let {num1, num2} = msgEvent.data postMessage(Number(num1) + Number(num2)) }
This can be done functionally (non file)
function createWorker(workerFunc) { if (! (workerFunc instanceof Function)) { throw new Error('Argument must be function'); } const src = `(${workerFunc})();`; const blob = new Blob([src], {type: 'application/javascript'}); const url = URL.createObjectURL(blob); return new Worker(url); }
Channel communication
The MessageChannel interface allows us to create a new message channel through its two MessagePort Property to send data.
Example of implementing the above dual iframe:
Port1[iframe2] <===> Port2[iframe1]
const [ifr1, ifr2] = document.querySelectorAll('iframe') const ifr1Window = ifr1.contentWindow const ifr2Window = ifr2.contentWindow // Monitor [iframe] onmessage information ifr1Window.addEventListener("message", function (msgEvent) { ifr1Window.document.querySelector('#btn').addEventListener('click', function () { // Transfer the value according to [port2] msgEvent.ports[0].postMessage(ifr1Window.document.querySelector('#data').value) }) }) ifr2Window.addEventListener("load", function () { // Establish information channel const channel = new MessageChannel() // Listen to the information of [port1] channel.port1.onmessage = function (msgEvent) { ifr2Window.document.querySelector('#message').innerHTML = msgEvent.data } // Send information to iframe1 [carry port2] ifr1Window.postMessage('I am ready', '*', [channel.port2]) })
iframe value transfer transformation
It can realize the value transfer between multiple iframe s, and more importantly, it can realize the value transfer between multiple web worker s.
Main page
const channel = new MessageChannel() const w1 = new Worker('worker1.js') const w2 = new Worker('worker2.js') w1.postMessage('index', [channel.port1]) w2.postMessage('', [channel.port2]) w2.onmessage = function (msgEvent) { console.log(msgEvent.data) }
worker1.js
onmessage = function ({data, ports}) { const port1 = ports[0] port1.postMessage(`${data} => worker1`) }
worker2.js
onmessage = function({ports}) { const port2 = ports[0] port2.onmessage = function (msgEvent) { postMessage(`${msgEvent.data} => worker2`) } }
index => worker1 => worker2
- index: ① issue port1/2 through worker1/2; ② Monitor worker2.
- worker1: ① monitor worker1; ② port1 sends a message.
- Worker2: ① monitor worker2; ② Monitor port2; ③ The worker sends a message.
comlink
The RPC proxy method adopted by Comlink is not to pass the context (because it is very dangerous, and the function passing will lead to Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Worker': xxx could not be cloned Error reporting).
In essence, it is still MessagePort message communication, but it encapsulates the "operation judgment" we have a headache and handles it in a more elegant way (Proxy + Promise).
RPC: Remote Procedure Call , remote procedure call refers to calling methods different from the current context. It can usually be different threads, domains and network hosts through the provided interface.
index.html
import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs" const worker = new Worker("worker.js") const cw = Comlink.wrap(worker) comput.addEventListener('click', async function () { let result = await cw(num1.value, num2.value) dom.innerHTML = result })
worker.js
importScripts("https://unpkg.com/comlink/dist/umd/comlink.js") function add (num1, num2) { return Number(num1) + Number(num2) } Comlink.expose(add)
importScripts() synchronously imports one or more scripts into the scope of the worker. Subordinate to: WorkerGlobalScope Interface.
Reference link
- https://developer.mozilla.org/zh-CN/docs/Web/API/MessageEvent
- https://www.zhangxinxu.com/wordpress/2012/02/html5-web-messaging-cross-document-messaging-channel-messaging/
- https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel