skip to content

globalThis and structuredClone

Two runtime built-ins that solve long-standing pain: globalThis gives one portable name for the global object across browsers, workers and Node, and structuredClone does real deep copying including cycles. Interviewers ask because most candidates still reach for JSON.parse(JSON.stringify(x)).

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

What does structuredClone(value) give you that a JSON.parse(JSON.stringify(value)) round trip does not, and which values does it refuse to clone?

level: middleimportance: must knowfreq 60%

answer

  1. one is a real copy, one is a serialisation
  2. cycles and shared references survive
  3. behaviour cannot be copied
  4. the prototype does not come along
  5. it throws instead of dropping

basics

~20 s

structuredClone performs a real deep copy: it preserves cycles and shared references and reproduces Date, RegExp, Map, Set, ArrayBuffer and typed arrays as themselves. It throws a DataCloneError for functions and symbols, and it does not preserve prototypes.

solid answer

~50 s

A JSON round trip is a serialisation, not a copy: it only knows the JSON data model, so `Date` becomes a string, `Map` and `Set` become `{}`, `undefined` and function-valued properties vanish, and a cycle throws. `structuredClone` implements the structured clone algorithm, which walks the object graph directly. It reproduces `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, typed arrays and `Error` objects as the same kinds of things, it preserves cycles, and it preserves shared references — if two properties pointed at one object, the clone's two properties point at one clone. What it will not do is carry behaviour: a function or symbol anywhere in the graph makes the whole call throw a `DataCloneError`, and a class instance comes back as a plain object with the same data but no prototype, so its methods and `instanceof` are gone.

code

javascript · 24 lines
javascript
const shared = { id: 1 };
const state = {
  when: new Date('2020-01-01'),
  tags: new Set(['a', 'b']),
  by: new Map([['k', 1]]),
  left: shared,
  right: shared,
};
state.self = state;

const copy = structuredClone(state);

console.log(copy.when instanceof Date); // true
console.log(copy.tags.has('a'));        // true
console.log(copy.by.get('k'));          // 1
console.log(copy.self === copy);        // true
console.log(copy.left === copy.right);  // true
console.log(copy.left === shared);      // false

try {
  structuredClone({ run() {} });
} catch (e) {
  console.log(e.name); // "DataCloneError"
}

go deeper

for a junior

Know that structuredClone is the built-in deep copy and that the JSON round trip is not one: dates, Maps and Sets do not survive it, and cycles make it throw.

for a middle

Be ready to list what the clone algorithm reproduces, explain that cycles and shared references are preserved, and state precisely what happens to a function in the graph and to a class instance's prototype.

for a senior

Demonstrate judgment about where copies belong at all — which boundaries need a defensive snapshot, and how to shape state so that data is cloneable and behaviour lives elsewhere.

for a principal

Own the data-shape rule for the codebase: state that crosses a clone, a worker or a storage boundary must be plain serialisable data, with rehydration into rich objects as an explicit, tested step rather than an assumption.

## The two things people mean by "copy" A **shallow** copy (`{ ...obj }`, `Object.assign({}, obj)`) makes a new outer object whose properties still point at the same nested objects. A **deep** copy makes new objects all the way down, so mutating the copy cannot be observed through the original. The JavaScript standard library had no deep-copy function for a long time, which is why `JSON.parse(JSON.stringify(x))` became folklore. ## Why the JSON round trip is not a deep copy It is a *serialisation followed by a parse*, and it can only express what the JSON data model can express. Going through it, at the level that matters for copying: - values with no JSON representation — `undefined`, functions, symbols — are dropped from objects and become `null` inside arrays; - a `Date` comes back as a string, because serialising it produced a string; - `Map`, `Set`, `RegExp` and class instances come back as ordinary objects, usually empty ones, because their state is not in enumerable own properties; - `BigInt` throws; - a cycle throws, because there is no way to write one down. So the round trip works only for graphs that were already plain JSON-shaped data, and fails *silently* for most of the interesting cases — which is what makes it dangerous rather than merely limited. ## What structuredClone does instead `structuredClone(value)` runs the **structured clone algorithm**, which walks the live object graph and builds a parallel graph of the same kinds of objects. Types it reproduces as themselves include `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, `DataView`, the typed arrays, and `Error` objects; in a browser it also handles host types marked serializable, such as `Blob`, `File` and `ImageData`. It handles graph shape, not just tree shape: ```js const state = { when: new Date(), tags: new Set(['a']), by: new Map([['k', 1]]) }; state.self = state; // a cycle const shared = { id: 1 }; state.left = shared; state.right = shared; // two paths to one object const copy = structuredClone(state); copy.when instanceof Date; // true copy.tags.has('a'); // true copy.self === copy; // true — cycle preserved, not infinite copy.left === copy.right; // true — sharing preserved copy.left === shared; // false — but it is a new object ``` That cycle-and-sharing preservation is the property a hand-written recursive clone usually gets wrong, and it is the strongest single reason to prefer the built-in. ## What it refuses: DataCloneError The algorithm has no way to copy behaviour, so it does not try. A function or a symbol anywhere in the graph makes the call throw a `DataCloneError` (a `DOMException`), and the throw is **all-or-nothing**: you do not get a partial clone with the offending property omitted. ```js structuredClone({ n: 1, onDone() {} }); // DataCloneError: onDone() {} could not be cloned. ``` Browser DOM nodes are likewise not cloneable and throw. Note that this differs from `JSON.stringify`, which drops functions quietly — structuredClone's loudness is a feature: it tells you the value you were about to copy contained something a copy cannot represent. ## What it changes silently The important silent transformation is **prototypes are not preserved**. Cloning an instance of your own class produces a plain object carrying the same own data properties, with `Object.prototype` as its prototype: ```js class User { constructor(name) { this.name = name; } greet() { return `hi ${this.name}`; } } const clone = structuredClone(new User('ada')); clone.name; // 'ada' clone instanceof User; // false clone.greet; // undefined ``` In the same vein, accessor properties are read and stored as plain data, and property attributes such as non-enumerable or non-writable are not carried across. Private class fields (`#x`) are not own properties at all, so they do not survive either. `structuredClone` also ignores `toJSON()` — that hook belongs to JSON serialisation and has no role in the clone algorithm. ## Where it comes from and where it is available `structuredClone` is a global function defined by the HTML specification rather than by ECMAScript, which is why you will not find it in a language reference under `Object`. The underlying algorithm has been in the platform for years as the mechanism behind `postMessage` and IndexedDB storage; exposing it as a callable function is the recent part. It is available in evergreen browsers from early 2022 and in Node from version 17. ## How to decide Use `structuredClone` when you need a deep copy of *data* — state snapshots for undo, defensive copies before a mutating routine, detaching a value from a reactive store. Use a manual or library clone when the graph contains behaviour or when you must preserve class identity. Use `JSON.parse(JSON.stringify(x))` essentially never as a clone: if the data really is JSON-shaped, structuredClone copies it correctly too and faster; if it is not, the JSON round trip corrupts it quietly.

  • Two properties of an object point at the same nested object. What is true of them after structuredClone?
    They still point at one object — a single new clone reachable from both paths. The algorithm keeps a memory of what it has already cloned, which is the same mechanism that lets it terminate on cycles. A JSON round trip loses this: each path is serialised independently, so the parse produces two separate objects with equal contents.
  • Does structuredClone use toJSON() the way JSON.stringify does?
    No. `toJSON()` is a hook of the JSON serialisation algorithm only. The structured clone algorithm never calls it, so a class that relies on `toJSON()` to flatten itself for the wire gets no such treatment here — its own data properties are copied and its prototype is dropped.
  • Why is structuredClone throwing on a function arguably better than JSON.stringify silently dropping it?
    Because a dropped function turns into a bug far from its cause: something later calls `copy.onDone()` and gets a TypeError with no hint that a copy removed it. Throwing at the copy site names the offending property immediately, and it forces an explicit decision — restructure the value so data and behaviour are separate, or clone it some other way.

saying these in an interview costs you the question

  • Says JSON.parse(JSON.stringify(x)) is a proper deep copy
  • Expects class methods or instanceof to survive a clone
  • Thinks a cycle causes structuredClone to hang or overflow
  • Assumes non-cloneable properties are skipped rather than thrown on
  • Believes structuredClone respects toJSON()

context

open as a page

What is globalThis in JavaScript, and what problem does it solve that window, self, and global did not?

level: juniorimportance: should knowfreq 55%

basics

~20 s

globalThis is the single standard name for the global object in every JavaScript environment. Browsers expose window, workers expose self, Node exposes global; globalThis, added in ES2020, resolves to the right one everywhere without feature sniffing.

open as a page

What does top-level `this` evaluate to in a classic browser script, in an ES module, and in a Node CommonJS module, and why is `this` an unreliable way to reach the global object?

level: middleimportance: should knowfreq 45%

basics

~20 s

Top-level this is the global object in a classic script, undefined in an ES module, and module.exports in a CommonJS module. Because the value depends on how the file is loaded, use globalThis when you actually want the global object.

open as a page

You need a deep copy of an application state object that holds class instances, an options object with a callback in it, and a back-reference cycle. What happens if you call structuredClone on it, and how do you design around the parts it cannot handle?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The call throws a DataCloneError because of the callback, and it fails entirely rather than partially. The cycle would have been fine and the class instances would have survived as plain data without methods, so the fix is to separate cloneable state from behaviour and rehydrate explicitly.

open as a page

What does the transfer option of structuredClone(value, { transfer }) do, and what state is the original ArrayBuffer left in afterwards?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

Listing an ArrayBuffer in the transfer option moves its memory into the clone instead of copying it. The original buffer is left detached: its byteLength becomes 0 and it no longer holds any data, so nothing may read through it again.

open as a page