How it works
The spec describes an abstract machine. It never says how to parse fast or lay out memory; it says what observable result an algorithm must produce, and an engine may do anything that gives the same results. That is why V8 and SpiderMonkey can be built so differently and still run the same programs.
The spec is deliberately incomplete at the edges. It marks those places as host-defined and defines a host hook: an abstract operation whose behaviour the host supplies. Examples are HostEnqueuePromiseJob (how a promise reaction becomes a queued Job), HostEnsureCanCompileStrings (lets a host forbid eval, which is how Content-Security-Policy hooks in), and HostLoadImportedModule (how import specifiers are resolved and fetched). The engine implements the language; the host implements the hooks and adds host objects to the global object.
// Three promise callbacks, one timer. The spec fixes the order of the
// promise jobs; where the timer lands relative to I/O is up to the host.
setTimeout(() => console.log('timer'), 0);
Promise.resolve().then(() => console.log('job 1'));
queueMicrotask(() => console.log('job 2'));
console.log('sync');
// → sync
// → job 1
// → job 2
// → timer
Promise.then jobs are specified. setTimeout is host-defined. Browsers follow the HTML Standard's event loop, which runs a microtask checkpoint after each task; Node.js 24 prints the same order here.
Edge cases
- Global object contents. The spec requires
globalThis and the standard built-ins on it. Everything else is the host's choice: window/self in browsers, global in Node.js, document only with a DOM.
- Same name, different behaviour.
console is not in ECMA-262. Output format, async behaviour, and available methods differ across hosts.
- Hosts can add language-adjacent features.
import.meta is specified as syntax, but its properties come from the host (HostGetImportMetaProperties): import.meta.url in browsers and Node.js, import.meta.dirname in Node.js.
- Spec edition vs. implementation. A browser or runtime does not implement "ES2025" as a unit. Each engine ships features individually, so check support per feature, not per edition.
- Legacy: Web browsers also implement Annex B, a normative part of the spec that documents legacy web-compatibility behaviour only required of web-browser hosts.
Real-world scenario
A utility library runs in the browser and in Node.js. It uses structuredClone, setTimeout, and Array.prototype.at. at is ECMAScript, so its availability depends only on the engine version. setTimeout exists in both hosts, but Node.js returns a Timeout object while browsers return a number, which is the host difference that breaks typeof id === 'number' checks. structuredClone is a host API (defined in the HTML Standard and added by Node.js), so the library must check the host, not the engine, before using it.
Trade-offs
Splitting spec, engine, and host lets one language run in browsers, servers, and embedded devices, and lets engines compete on speed. The cost is that "it works in my runtime" never proves it is JavaScript. Writing portable code means sticking to ECMA-262, feature-detecting host APIs, and knowing which layer a bug report belongs to: a spec bug, an engine bug, or a host difference.