cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Messaging & structured cloning

Send data safely between windows, workers, iframes, and tabs with structuredClone, MessageChannel, BroadcastChannel, and transfer lists.

You will learn to
  • 01
    Clone richer dataUse structuredClone for Dates, Maps, Sets, buffers, errors, and circular references.
  • 02
    Choose a message pathPick MessageChannel for a private line and BroadcastChannel for same-origin announcements.
  • 03
    Move big bytesExplain when transfer lists detach ArrayBuffers and why that can be faster than copying.

Data crossing a boundary

Modern JavaScript is rarely alone. A page talks to an iframe. A window sends work to a worker. A settings tab tells every other open tab that the theme changed. Those messages need a safe way to carry data across a boundary without sharing the same object by reference.

The browser solves that with the structured clone algorithm. It is the set of rules behind structuredClone(), postMessage(), MessagePort, BroadcastChannel, IndexedDB values, and worker messages. It copies many real JavaScript data types, preserves cycles, and can also transfer ownership of some binary objects instead of copying them.

Real-life analogyTransfer vs copy is an original document vs a photocopy

If you send a photocopy of a form, you keep your original. If you mail the original form, the other office has it and you do not. JavaScript messages make the same choice: copy most data, or transfer ownership of special objects such as ArrayBuffer.

In real life: Mailing a photocopy
In JavaScript: Copying with structured clone: both sides have their own data
In real life: Mailing the original
In JavaScript: Transferring: the receiver owns it and the sender no longer can use it
In real life: A big binder takes time to copy
In JavaScript: Large ArrayBuffers can be expensive to clone
In real life: The courier must know what can be shipped
In JavaScript: Uncloneable values throw DataCloneError

Where the analogy stops: Real documents can be scanned or returned later. A transferred ArrayBuffer is detached immediately; the sender cannot read those bytes unless someone sends another buffer back.

This lesson sits between Windows, iframes & postMessage and Web Components in the curriculum. Those neighboring lessons are text-only in this batch, so this one focuses on the shared data-moving rules you will use in both windows and workers.

structuredClone copies richer data than JSON

INTERACTIVE

structuredClone(value) deep-copies a value using browser messaging rules. Compared with JSON, it keeps many JavaScript-specific types: Date, Map, Set, RegExp, ArrayBuffer, typed arrays, Blob, File, common Error objects, and circular references.

It is still a data copier, not a magic duplicator of everything. It does not preserve custom prototypes, property descriptors, accessor functions, or methods from your own class instances. It reads own enumerable properties, stores their current values, and rebuilds supported built-ins on the other side.

Real-life analogystructuredClone is a careful moving company

Imagine movers who carefully list every item in your apartment, pack it, and rebuild matching furniture in a new apartment. The copy is useful and separate, but it is not the exact original. That is why changing a cloned array does not change the source array, and why a class instance arrives as plain data.

In real life: Pack furniture, dishes, and labeled boxes
In JavaScript: Copy Dates, Maps, Sets, arrays, buffers, and nested objects
In real life: Rebuild furniture in the new house
In JavaScript: Create fresh objects that are not the same references
In real life: Refuse things that cannot be packed
In JavaScript: Throw for functions, DOM nodes, symbols, WeakMap, and WeakSet
In real life: Leave the designer brand tag behind
In JavaScript: Your class prototype and methods are not preserved

Where the analogy stops: A moving company might move a lamp and the electricity still works. structuredClone does not move behavior: methods, closures, and DOM connections are outside the data box.

Clone inspector
Inspector shapePop out in the code editor (opens in a new tab)JavaScript
const original = makeValue(choice);const copy = structuredClone(original);inspect(copy);
Resultwaiting

Choose a value and press Inspect clone.

Try it yourself

Pick a value, read the code, then inspect. The result comes from your browser's real structuredClone implementation.

No class names are read from runtime names here; labels are explicit so production minification cannot change the lesson.

Try the class instance and getter options. The class example keeps its own fields but loses its method because the prototype was not shipped. The getter example runs the getter once and stores the returned number as a regular value. That is very different from copying a live accessor.

What cannot be cloned, and what changes

SORT

The rule of thumb is: data can travel; behavior and live host objects usually cannot. A function closes over a scope. A DOM node is attached to a document. A symbol represents identity. A WeakMap depends on garbage-collection reachability. The clone algorithm refuses these with a DataCloneError instead of pretending it copied them.

Can it be cloned?
  • Date
  • Map with object keys
  • Set
  • Object that points to itself
  • RegExp with lastIndex = 5
  • Object property with a getter
  • Instance of your own class
  • Non-enumerable property
  • Function
  • DOM node
  • Symbol value
  • WeakMap
Try it yourself
0 of 12 correct

Sort each value into the result you expect from structuredClone. Pay attention to the changed-but-not-throwing cases.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Only own enumerable properties copy

For plain objects and your own instances, structured cloning walks own enumerable properties. Non-enumerable properties disappear, accessors become data values, and property attributes such as writable/configurable are not preserved. The Enumerability & ownership and Property flags & descriptors lessons go deeper on those words.

One small built-in surprise: RegExp objects clone, but lastIndex is reset to 0. If you are mid-way through a global regular-expression search, do not rely on the clone continuing from the same spot.

MessageChannel and MessagePort

INTERACTIVE

MessageChannel creates two linked MessagePort objects: port1 and port2. Posting a message on one port delivers it to the other. You can keep one port and transfer the other to a worker or iframe, creating a private line that does not rely on global window message listeners.

Real-life analogyMessageChannel is a private walkie-talkie pair

A normal window message is like calling across a lobby: everyone nearby might have a listener. A MessageChannel is more like handing someone the other half of a private walkie-talkie pair.

In real life: Two walkie-talkies tuned to each other
In JavaScript: port1 and port2 from one MessageChannel
In real life: Hand one radio to another person
In JavaScript: Transfer a port to an iframe or worker
In real life: Speak into one; the other hears
In JavaScript: postMessage on one port fires message on the other
In real life: Turn the radio off
In JavaScript: port.close() stops the line

Where the analogy stops: Real walkie-talkies can be overheard. A MessageChannel port is only usable by code that has been given that port, though the messages still use structured cloning rules.

MessageChannel lab: a private line
const channel = new MessageChannel();channel.port1.onmessage = (event) => log("port1", event.data);channel.port1.start();iframe.contentWindow.postMessage("connect", "*", [channel.port2]);channel.port1.postMessage("hello iframe");
Private logport1 ↔ iframe
  • Press Connect iframe to transfer port2.
Try it yourself

MessageChannel creates two ports. This lab transfers port2 to a sandboxed iframe, then uses port1 to talk privately. port1 uses addEventListener plus start; the iframe uses onmessage.

The iframe is sandboxed with scripts only. It validates that the setup message came from its parent before accepting the port.

The lab uses both styles you will see in real code. The parent uses addEventListener("message", ...), so it calls port.start(). The iframe uses port.onmessage = ..., which starts delivery automatically in browsers. Calling start() anyway is harmless and often makes the intent clear.

BroadcastChannel

INTERACTIVE

BroadcastChannel is for announcements, not private conversations. Every browsing context on the same origin that opens a channel with the same name can hear messages from the others: tabs, windows, iframes, and workers. The object that sends a message does not receive its own message.

Real-life analogyBroadcastChannel is a building intercom

Picture an apartment building intercom. One announcement goes to everyone listening to that channel in the building. BroadcastChannel is great for “settings changed”, “log out everywhere”, or “cache updated” messages.

In real life: Everyone tuned to channel 3 hears the announcement
In JavaScript: Every same-origin BroadcastChannel with the same name receives it
In real life: The person speaking already knows what they said
In JavaScript: The sending channel object does not receive its own message
In real life: Only this building hears it
In JavaScript: Different origins do not share channels

Where the analogy stops: A building intercom might reach every apartment no matter who owns it. BroadcastChannel is stricter: same channel name is not enough; same origin is required.

BroadcastChannel lab: a same-origin intercom
Three listeners, one channel namePop out in the code editor (opens in a new tab)JavaScript
const a = new BroadcastChannel("lesson-room");const b = new BroadcastChannel("lesson-room");const c = new BroadcastChannel("lesson-room");b.onmessage = (event) => log("B heard " + event.data);c.onmessage = (event) => log("C heard " + event.data);a.postMessage("standup at 10");
Intercom logsame origin
  • Press Create channels, then broadcast from A, B, or C.
Try it yourself

Every BroadcastChannel object with the same name and origin hears messages from the others. The sending object does not receive its own message. Open this lesson in a second tab to see same-origin cross-tab delivery too.

Reset closes the channels so this page stops listening.

Try opening this lesson in another tab after creating the channels. BroadcastChannel is one of the few experiments here that becomes more fun with two tabs, because the second tab is another same-origin listener.

Transferring vs copying

INTERACTIVE

Copying makes independent data on both sides. Transferring moves ownership of a transferable object. The clearest example is ArrayBuffer. After transfer, the sender’s buffer is detached; its byteLength becomes 0, and the receiver owns the bytes.

Transfer lab: copy or move the bytes
Copy and transferPop out in the code editor (opens in a new tab)JavaScript
const buffer = new ArrayBuffer(8);const copy = structuredClone(buffer);console.log(buffer.byteLength, copy.byteLength); const moved = structuredClone(buffer, { transfer: [buffer] });console.log(buffer.byteLength, moved.byteLength);
Buffer logreal byteLength
  • Run a copy, a structuredClone transfer, or a postMessage transfer.
Try it yourself

Copying leaves the original usable. Transferring moves ownership: the sender's ArrayBuffer is detached, so byteLength becomes 0. Timings vary by browser and machine, so compare the byte lengths first.

Use transfer for large binary data when the sender no longer needs the original bytes.

Transfer lists appear in two places you will use often: structuredClone(value, { transfer: [buffer] }) and port.postMessage(value, [buffer]). They are perfect for large images, audio chunks, or binary protocol frames when the sender is done with the data.

Copying, private messaging, and broadcast messaging
QuestionstructuredCloneMessageChannelBroadcastChannel
Best forDeep-copying JavaScript data inside one realmSending one message to one other contextAnnouncing to all same-origin listeners
ConnectionNo connection; returns a copyTwo linked MessagePort objectsSame string channel name and same origin
Sender receives its own message?Not a messaging APIOnly if the other port sends backNo, the BroadcastChannel object that posts does not receive its own message
Transfer listYes, with structuredClone(value, { transfer })Yes, with port.postMessage(value, transfer)No practical transfer list in the common browser API

Message delivery order

STEP THROUGH

postMessage returns immediately. The receiving message event runs later, after the current task finishes. That timing connects this lesson to the Event loop lesson: posting a message schedules work; it does not pause the current line of code and jump to the handler.

Step through postMessage order
Step 0 of 6Ready
Your turn: follow the blue line

Step through postMessage order. Switch between copying and transferring, then predict which log appears first and what byteLength the sender keeps.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const buffer = new ArrayBuffer(4); port1.onmessage = (event) => {  console.log("message event:", event.data.byteLength);}; port2.postMessage(buffer);console.log("after postMessage:", buffer.byteLength);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
How should line 8 send the buffer?

Changing this line starts a fresh run. Predict line 9 before you step.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The setting changes one visible line. In copy mode, line 9 sees the sender’s buffer still attached. In transfer mode, line 8 detaches the sender immediately, so line 9 logs 0. In both modes, the receiver’s event appears later and sees the four bytes.

Where you will use this

PRACTICAL

These APIs show up whenever a web app splits work across places: a canvas sends pixels to a worker, an iframe receives a private reply port, a settings page tells other tabs to refresh, or a worker returns parsed data without blocking the main interface.

Worker image job with transferJavaScript
const worker = new Worker("image-worker.js", { type: "module" });const pixels = canvasBuffer; worker.postMessage(  { job: "blur", width, height, pixels },  [pixels],); worker.onmessage = (event) => {  drawPixels(event.data.pixels);};

The important design choice is visible on lines 3–6. The message object carries ordinary metadata by copy, while the big pixel buffer is in the transfer list. The main page should not read that buffer after sending it; ownership moved to the worker. When the worker finishes, it can transfer a new buffer back.

Choose the smallest tool
  • Need a deep copy inside one script? Use structuredClone.
  • Need one private two-ended line? Use MessageChannel.
  • Need same-origin announcements across tabs or workers? Use BroadcastChannel.
  • Need to send huge binary data and give it up? Add a transfer list.

Common misconceptions

“structuredClone is JSON.parse(JSON.stringify(...)), but nicer.”

No. JSON makes text and loses Dates, Maps, Sets, undefined properties, circular references, and more. structuredClone copies many richer values without creating text.

“A deep clone preserves class behavior.”

Built-in brands such as Date and Map survive. Your own class prototype does not. The clone gets own enumerable fields, not methods or private fields.

“postMessage runs the receiver immediately.”

It returns before the receiver’s message event runs. That is why the order experiment logs the line after postMessage first.

“BroadcastChannel is global to every website.”

It is same-origin. A channel named "updates" on your site is separate from a channel with the same name on another origin.

“Transfer is just a faster copy.”

Transfer changes ownership. The sender’s ArrayBuffer is detached, so code that expects to keep reading it will break.

“If it can be stored in an object, it can be cloned.”

Objects can hold functions, DOM nodes, symbols, and WeakMaps. structuredClone refuses those because they are behavior, live host objects, unique identities, or weak collections.

Practice: clone it, send it, move it

5 EXERCISES
Exercise 1 · Warm-upPredict a real clone

Predict both lines, then run the snippet in your console or with Node 22.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const original = { items: ["a"], when: new Date("2024-01-01T00:00:00.000Z") };
const copy = structuredClone(original);
copy.items.push("b");
console.log(original.items.join(""));
console.log(copy.when instanceof Date);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeDetach the buffer

    What byte lengths print after transferring an ArrayBuffer?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const buffer = new ArrayBuffer(16);
    const moved = structuredClone(buffer, { transfer: [buffer] });
    console.log(buffer.byteLength);
    console.log(moved.byteLength);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the bug: sending a callback

      A teammate tries worker.postMessage({ done: () => alert("ok") }). What error name should they expect?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeGetter or value?

        Predict what happens to a getter when an object is cloned.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const source = { get answer() { return 42; } };
        const copy = structuredClone(source);
        console.log(copy.answer);
        console.log(typeof Object.getOwnPropertyDescriptor(copy, "answer").get);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePick the private-line API

          You have an iframe and want a dedicated private connection after an initial handshake. Which API creates that line?

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS

            Each answer explains the rule it tests. Read the explanations for wrong answers too; they are the common bugs.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Which statement best describes structuredClone?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does the Date clone check print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const copy = structuredClone(new Date("2024-01-01T00:00:00.000Z"));
              console.log(copy instanceof Date);
              console.log(copy.toISOString());

              Choose an answer to see the explanation.

            3. Question 3 of 8What happens to RegExp lastIndex?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const re = /go/g;
              re.lastIndex = 2;
              const copy = structuredClone(re);
              console.log(copy.source);
              console.log(copy.lastIndex);

              Choose an answer to see the explanation.

            4. Question 4 of 8What does the getter clone snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const original = { get score() { return 9; } };
              const copy = structuredClone(original);
              console.log(copy.score);
              console.log(typeof Object.getOwnPropertyDescriptor(copy, "score").get);

              Choose an answer to see the explanation.

            5. Question 5 of 8Which API is the private walkie-talkie pair?

              Choose an answer to see the explanation.

            6. Question 6 of 8What does transfer do to an ArrayBuffer sender?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const buffer = new ArrayBuffer(8);
              const moved = structuredClone(buffer, { transfer: [buffer] });
              console.log(buffer.byteLength);
              console.log(moved.byteLength);

              Choose an answer to see the explanation.

            7. Question 7 of 8Who receives a BroadcastChannel message from channel A?

              Choose an answer to see the explanation.

            8. Question 8 of 8What does the message order snippet print first in transfer mode?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const buffer = new ArrayBuffer(4);
              structuredClone(buffer, { transfer: [buffer] });
              console.log("after transfer:", buffer.byteLength);

              Choose an answer to see the explanation.

            Key takeaways

            • structuredClone deep-copies many data values that JSON cannot, including Date, Map, Set, typed arrays, Error objects, and cycles.
            • It refuses behavior and live objects such as functions, DOM nodes, symbols, WeakMap, and WeakSet.
            • Your own class instances arrive as plain enumerable data; prototypes, methods, accessors, and descriptors are not preserved.
            • MessageChannel gives two private ports; BroadcastChannel announces to same-origin listeners with the same name.
            • A transfer list moves ownership. For ArrayBuffer, the sender’s byteLength becomes 0.

            Remember the one-liner.
            Structured cloning is the browser’s safe data-packing system for copies and messages; transfer lists move ownership instead of making another copy.

            Up next: Web Components, where custom elements, Shadow DOM, templates, and slots help you build reusable pieces of interface.

            CompleteFrontend Clear concepts. Working examples.