Cross-frame bugs, workers, vm sandboxes and multi-threaded code all follow from this model.
The ECMAScript specification defines a few abstract building blocks that explain behaviour you'd otherwise find mysterious.
A realm is a global environment: a global object plus its own complete set of intrinsics — Array, Object.prototype, Promise, and so on. Each window, iframe, worker and Node vm context has its own realm. Objects can flow between realms in the same thread (e.g. a parent page reading an iframe's array), but their prototypes are that realm's intrinsics. Hence:
iframeArray instanceof Arrayis false in the parent — it inherits from the iframe'sArray.prototype. UseArray.isArray, which works across realms.Object.prototype.toString.call(x)and duck typing are more robust thaninstanceoffor values that may cross realms.Symbol.forsymbols are shared across realms; other symbols and intrinsics are not.
An agent is a thread of execution: it has its own execution-context stack, its own job (microtask) queues and its own event loop provided by the host. A window and its same-origin iframes may share one agent (they're synchronously reachable), while each Worker is a separate agent. Agents communicate by message passing (structured clone, transferables) — or, within an agent cluster, by sharing memory through SharedArrayBuffer.
The spec delegates scheduling to the host (the browser via the HTML standard, or Node.js) through host hooks: that's why the event loop details live in the HTML spec, not ECMAScript, and why Node has extra phases.
A proposal called ShadowRealm would let code create a fresh, isolated realm synchronously, for sandboxing plugins and tests; until it ships, iframes, workers and Node's vm module fill that role.
Example
// Run with Node.js: each vm context is a separate realm
import vm from "node:vm";
const foreignArray = vm.runInNewContext("[1, 2, 3]");
console.log(foreignArray instanceof Array); // false: different Array intrinsic
console.log(Array.isArray(foreignArray)); // true: realm-independent check
console.log(Object.prototype.toString.call(foreignArray)); // [object Array]
const ForeignObject = vm.runInNewContext("Object");
console.log(ForeignObject === Object); // false: separate intrinsics
const sym = vm.runInNewContext('Symbol.for("app.id")');
console.log(sym === Symbol.for("app.id")); // true: registry is shared
// structuredClone copies into *this* realm's intrinsics
const local = structuredClone(foreignArray);
console.log(local instanceof Array); // trueRemember
- A realm = global object + its own intrinsics; iframes, workers and vm contexts each have one
instanceoffails across realms — useArray.isArrayor brand checks- An agent = thread with its own stack, job queues and event loop; workers are separate agents
Watch out for
- Using
instanceof Arrayorinstanceof Erroron values that came from an iframe or vm context.