Lesson 0002 · ~12 minutes

What actually clones: keep, flatten, throw

One skill: look at a value and predict keep / flatten / throw — so you stop treating a clone as a document or a living class.

Win for this lesson

You can say: “that Date stays a Date; that method throws; that Invoice becomes { n } with Object.prototype — and a REST body still needs JSON.stringify.”

1. The two misses, in one sentence

Scenario B and D were the same mix-up. structuredClone() returns another JavaScript value. It is not HTTP bytes, and it is not your class with methods. A REST API wants a JSON document. An Invoice that other services must read for years wants a custom serializer (version + revival). The type matrix is how you see that.

2. Three buckets

MDN lists supported types. The HTML walk decides what happens. Compress it to three buckets:

Keep

Same kind, new identity. Date, Map, Set, arrays, buffers, Blob/File, cycles, Error, undefined keys, most primitives (not symbol).

Flatten

New ordinary object. Enumerable own data only. Prototype, methods, private fields gone. Getters run once; the result is stored as a data property.

Throw

DataCloneError: functions, DOM nodes, symbol values, Promise, WeakMap, Proxy. Loud, unlike JSON’s silent drop.

Class instances land in flatten, not throw. That is why “clone the Invoice and write the file” is the wrong durability plan — you get { n: 3 }, not total() or #id.

3. How the walk actually works

For an ordinary object the spec (step 26.4) does this:

  1. Remember the object in a memory map. If it appears again, reuse that copy — cycles keep clone.self === clone.
  2. Visit enumerable own string keys only. Enumerable symbol keys are dropped (verified in current engines).
  3. Read each value with [[Get]]. A getter runs; the clone stores the returned number/string as a normal data field (no getter on the copy).
  4. Deserialize as a new Object. Your class prototype is not restored (MDN: prototype chain is not walked).

Built-ins with special slots (Date, Map, ArrayBuffer, …) take earlier branches and keep their kind. Callables, platform objects that are not [Serializable], Promises, WeakMaps, and Proxies throw.

Design take

Send data (DTOs). Rebuild behavior on each side. Same rule as worker messages — now you can predict the three outcomes instead of hoping.

4. Lab — same value, two algorithms

Click each preset. Left is structured clone. Right is JSON. Watch the type, the keys, and which side throws.

Things to notice: Date stays a Date on the left and becomes a string on the right. Map stays a Map vs {}. A cycle keeps «self» vs TypeError. A function throws on the left and is omitted on the right. A class instance flattens on both sides — clone is not revival.

5. Practice — predict the bucket

Equal-length options. Includes a second pass at B and D.

Snippet A

structuredClone(new Date("2020-01-02")) — result?

Snippet B

structuredClone({ n: 1, run() { return 1 } }) — result?

Snippet C · lesson 1 D

class Invoice { #id; total() {} } — clone of new Invoice()?

Snippet D · lesson 1 B

You already structuredClone(settings). The REST server wants application/json. Next step?

Snippet E

const o = { n: 1 }; o.self = o; then structuredClone(o).

6. What to remember

Ask your teacher If a Vue/MobX proxy, a File, or a getter surprise just bit you — ask. Those are flatten/throw cases in the wild.

Primary source (read next)

MDN — The structured clone algorithm. Supported types + “things that don’t work.” For the walk itself (memory map, enumerable own, [[Get]], Proxy/Promise throw), skim WHATWG StructuredSerializeInternal.