JS Internals

An analytical introduction · 19 chapters · 67 probes · 86 primary sources

JavaScript, below the surface.

A technical treatment of the language for engineers who already write it: the ECMAScript specification read as an executable abstract machine, and the engines read as implementations of that machine. Coercion becomes ToPrimitive with a hint; hoisting becomes FunctionDeclarationInstantiation; performance becomes Shapes, inline caches, and deoptimisation.

Method

Every claim is traced to one of three layers, marked throughout with a badge, and each chapter ends by naming the papers its mechanisms come from. Specification claims are paired with probes: snippets you run in a sandboxed Worker in your own browser, and whose outputs are also recorded across engines.

SpecEngineHostOrigin

62-term glossary of the abstract machine, linked from every chapter.

Three layers, kept separate

Spec

The abstract machine

ECMA-262 defines the language as algorithms over specification types: Completion Records, Reference Records, Property Descriptors, Environment Records, internal methods. Anything derived here holds in every conforming engine.

Engine

The engine's realisation

V8, SpiderMonkey, and JavaScriptCore implement those algorithms with tagged values, Shapes and inline caches, generational collectors, and tiered speculative compilers. This layer is invisible to correct programs and decisive for fast ones.

Host

The host embedding

The event loop, task sources, module loading, cross-origin isolation, and structured clone are defined by HTML and Node.js, not by ECMAScript. Knowing where the specification stops is how you know which document to read.

Chapters

  1. 01Spec5 probes

    The Specification as an Abstract Machine

    ECMA-262 is not prose about a language; it is an executable model of one.

    Every observable behaviour of JavaScript is the output of algorithms defined over a small set of specification types: Records, Completion Records, Property Descriptors, Environment Records. Reading the language through those algorithms replaces folklore ("coercion is weird") with mechanism ("ToPrimitive was invoked with hint default").

    Read chapter
  2. 02SpecEngine5 probes

    Values and Their Representations

    What the specification says a Number or String is, and what the engine actually allocates.

    ECMA-262 specifies Number as IEEE-754 binary64 and String as a sequence of UTF-16 code units, and stops there. Engines layer tagged pointers, small-integer encodings, ropes, slices, one-byte strings, and elements-kind lattices on top. Performance and several correctness edge cases live in the gap between the two descriptions.

    Read chapter
  3. 03SpecEngine3 probes

    Objects Under the Hood: Shapes, Hidden Classes, Inline Caches

    The specification models an object as a dictionary. No fast engine implements it as one.

    ECMA-262 describes properties as an ordered collection of key/descriptor pairs. Engines factor that description into a shared, immutable Shape (V8: Map or hidden class; JSC: Structure) plus a per-object slot array, and then cache lookups at each property-access site. Nearly all JavaScript performance intuition reduces to keeping Shapes stable and access sites monomorphic.

    Read chapter
  4. 04SpecEngine4 probes

    Prototypes, Function Objects, and Class Semantics

    Delegation, [[Call]] versus [[Construct]], and the parts of `class` that are not syntactic sugar.

    JavaScript's object model is a single mechanism, delegation through [[Prototype]], dressed in several syntaxes. This chapter treats the mechanism precisely: how [[Get]] and [[Set]] walk the chain with a separate receiver, what a function object actually contains, how `new` and `super` are defined, and where class syntax introduces semantics (define versus set, private names, brand checks, derived-constructor `this` TDZ) that cannot be expressed in ES5.

    Read chapter
  5. 05SpecEngine4 probes

    Scope, Environment Records, and Closures

    Hoisting is instantiation, TDZ is an uninitialised binding, and a closure is a pointer.

    Scope in JavaScript is a linked list of Environment Records built at precisely specified moments. Once you see FunctionDeclarationInstantiation and CreatePerIterationEnvironment as algorithms, `var` hoisting, the temporal dead zone, loop-closure behaviour, and the classic shared-context memory leak all become predictable rather than folklore.

    Read chapter
  6. 06SpecHostEngine4 probes

    The Execution Model: Agents, Jobs, Promises, and the Event Loop

    ECMA-262 defines Jobs. It does not define an event loop. The host does, and the difference matters.

    Asynchrony in JavaScript is layered: the specification defines agents, execution contexts, and a Job queue abstraction with exactly one ordering guarantee; HTML and Node then define task sources, microtask checkpoints, and phase ordering on top. Promise reaction timing, the `await` tick count, and ordering puzzles are all derivable once the layers are separated.

    Read chapter
  7. 07SpecEngine2 probes

    Memory: Allocation, Garbage Collection, and Liveness

    The specification defines liveness in one paragraph. The engine spends a hundred thousand lines implementing it.

    ECMA-262 says almost nothing about memory until WeakRef and FinalizationRegistry forced it to define what "live" means. Engines implement generational, incremental, concurrent, and parallel collectors whose behaviour is invisible to correct programs and dominant in the performance of real ones. This chapter covers both: the normative liveness model, and V8's Orinoco collector as a representative of how the model is realised.

    Read chapter
  8. 08Engine1 probe

    The Compilation Pipeline: From Source Text to Speculative Machine Code

    "Interpreted or JIT-compiled" undersells it: a modern engine is four compilers and a deoptimiser negotiating over type feedback.

    V8 runs source through a lazy parser, a bytecode interpreter (Ignition), a non-optimising baseline compiler (Sparkplug), a fast mid-tier optimiser (Maglev), and a full optimising compiler (TurboFan/Turboshaft). Each tier trades compile latency for execution speed, and all optimisation is speculation on recorded feedback that can be invalidated by deoptimisation. Understanding the tiers explains warm-up curves, performance cliffs, and why microbenchmarks lie.

    Read chapter
  9. 09SpecHost2 probes

    Modules, Linking, and Realms

    ES modules are a three-phase graph algorithm with live bindings, not a file-inclusion mechanism.

    The module system is where the specification is at its most algorithmic: Module Records, a depth-first linking pass that creates indirect bindings before any code runs, a post-order evaluation pass with explicit cycle handling, and an asynchronous extension for top-level await. Realms are the other half of the isolation story: every global, every intrinsic, and every module map is per realm, which is why identity checks across iframes and `vm` contexts fail in specific, predictable ways.

    Read chapter
  10. 10SpecEngine4 probes

    Metaprogramming: Proxies, Reflect, Iteration Protocols, and Invariants

    Proxies expose the internal methods to user code, but only within a fence of invariants that keep the rest of the language sound.

    A Proxy is an exotic object whose thirteen internal methods dispatch to handler traps. The trap surface is exactly the internal-method table from chapter 1, `Reflect` is the identity implementation of that table, and the invariants the engine enforces after each trap are what allow the specification's other algorithms to keep assuming things about objects. The iteration protocols and tagged templates round out the language's hook points.

    Read chapter
  11. 11SpecHostEngine2 probes

    Shared Memory, Atomics, and the ECMAScript Memory Model

    JavaScript is single-threaded per agent. Agents in a cluster are not, and the specification has a formal memory model to prove it.

    SharedArrayBuffer gives multiple agents a view onto the same bytes. To say what a program that races on those bytes may observe, ECMA-262 §29 defines a memory model in the style of C++11: events, happens-before, synchronizes-with, sequentially consistent atomics, and a precise definition of data races. This chapter states the model, shows what it permits, and covers the host-level machinery (agent clusters, cross-origin isolation, structured clone and transfer) that surrounds it.

    Read chapter
  12. 12SpecEngine6 probes

    Arithmetic from the Gates Up

    Booth's recoding, Karatsuba's split, and Ryū's shortest digits: the algorithms under `*`, `**`, and `toString`.

    JavaScript exposes three arithmetic worlds: 32-bit integers through the bitwise operators and Math.imul, binary64 through Number, and arbitrary precision through BigInt. Each is implemented by a classical algorithm with a known cost curve, and the conversion between numbers and their decimal text is itself a hard problem that was only solved optimally in the last decade. This chapter follows a multiplication from Booth's 1951 recoding to V8's Karatsuba threshold, and a double to its shortest correctly rounded string.

    Read chapter
  13. 13SpecEngine5 probes

    Regular Expressions as a Backtracking Virtual Machine

    ECMAScript regexes are not regular languages; they are programs for a machine with unbounded backtracking, and their cost model follows from that.

    The specification defines pattern matching operationally, as continuation-passing Matcher functions that try alternatives in a fixed order and back up on failure. That definition gives regexes their expressive power (backreferences, lookaround) and their exponential worst case (ReDoS). Engines compile patterns to bytecode or native code and add linear-time fallbacks, but the semantics stay the same. This chapter reads the specification's matcher, shows where backtracking explodes, and follows what the `u` and `v` flags change about the alphabet.

    Read chapter
  14. 14SpecHost3 probes

    The Web-Compatibility Constraint: Hyrum's Law as Normative Force

    Every observable behaviour of a deployed engine is an interface somebody depends on. The specification is shaped by that fact more than by any design principle.

    JavaScript cannot remove or change anything that existing pages observe, because the pages cannot be recompiled. This single constraint explains Annex B, the names `includes` and `flat`, the existence of `Symbol.unscopables`, why `typeof null` is an eternal bug, why `Array.prototype.contains` failed, and why proposals are now designed with web-compat telemetry before they are designed with taste. Understanding it is the difference between finding the language incoherent and reading it as a fossil record.

    Read chapter
  15. 15SpecEngine2 probes

    The Proposal Pipeline: How a Behaviour Becomes Normative

    A feature is not "in JavaScript" when it is designed. It is in JavaScript when two engines ship it and test262 agrees with both.

    ECMA-262 is revised continuously by TC39 through a staged process whose entrance criteria are executable: a specification text, a conformance test suite, and interoperable implementations. This chapter describes the stages as gates, follows two features through them (one that changed engines before the spec, one that changed the spec because of engines), and explains what "living standard", "ES2026", and "Stage 3" each mean for code you write today.

    Read chapter
  16. 16SpecHostEngine4 probes

    The Security Model: Shared Intrinsics, Pollution, Membranes, and Side Channels

    Every script in a realm shares one `Object.prototype`. Everything else about JavaScript security follows from that sentence.

    JavaScript has no ambient authority model, no capability checks, and one mutable set of intrinsics per realm. Prototype pollution, supply-chain attacks, and sandbox escapes are all consequences of that design; membranes, frozen intrinsics (Hardened JavaScript), realms, and Workers are the mechanisms the language offers to rebuild boundaries. Separately, the hardware leaks: Spectre turned any high-resolution timer into a read primitive, and the specification and the browsers changed shape around it. This chapter treats both layers with their mechanisms exposed.

    Read chapter
  17. 17Engine3 probes

    Reading the Engine: Traces, Snapshots, Profiles, and Natives

    The engine will tell you exactly what it did. Almost nobody asks.

    Everything the earlier chapters inferred from timings can be observed directly: V8 prints its optimisation and deoptimisation decisions, exposes its object representation through natives syntax, and serialises its heap as a dominator graph. This chapter teaches the tools as a reading skill: what a deopt trace line means, how to read a heap snapshot's retainer path, what a CPU profile's self time and total time measure, and how to record ground truth about Shapes instead of guessing from benchmarks. The Node-only probe here is recorded from Node with natives syntax enabled, because that is the only place it can run.

    Read chapter
  18. 18EngineSpec4 probes

    TypeScript Against the Runtime: What a Static Type Cannot See

    A TypeScript type is a claim about values at compile time. Shapes, elements kinds, and inline caches are facts about objects at run time. They overlap less than the syntax suggests.

    TypeScript is erased before any of the earlier chapters apply. Its structural type system deliberately says nothing about representation, so two values of one type can have different Shapes, an `enum` is an object with a reverse mapping, a discriminated union is a megamorphic site, and `as` changes nothing. This chapter maps the two type systems onto each other: where they agree (control-flow narrowing mirrors what the JIT's feedback sees), where they diverge, and how to write types that happen to keep the runtime fast. It also treats erasure itself as a specification question, now that ECMAScript has a Stage 1 proposal for type annotations as comments.

    Read chapter
  19. 19SpecHostEngine4 probes

    ECMA-402 and Temporal: The Other Specification, and the Hardest Data Type

    Locale-sensitive behaviour is a second standard with implementation-defined data, and time is a domain where the 1995 design cannot be patched.

    ECMA-262 defines JavaScript's semantics completely except for one deliberate hole: anything depending on human language or region is delegated to ECMA-402, the Internationalization API, whose results are shaped by locale data the specification does not fix. This chapter explains what that means for determinism and testing, reads the `Intl` objects as a protocol over CLDR and ICU, and then turns to `Date`, why it is unrepairable, and how `Temporal` (Stage 3, shipping) rebuilds the domain from exact types.

    Read chapter

Notation used throughout

The text uses the specification's own conventions so that every statement can be checked against tc39.es/ecma262.

[[Name]]
An internal slot or Record field: engine-private state such as [[Prototype]] or [[Environment]].
@@name
A well-known symbol, e.g. @@iterator is Symbol.iterator.
%Name%
An intrinsic object of the current realm, e.g. %Array.prototype%.
? Op()
Invoke Op and propagate an abrupt completion (throw/return/break) immediately.
! Op()
Invoke Op, asserting it cannot produce an abrupt completion.
CamelCaseName
An abstract operation defined in the specification, e.g. ToPrimitive or OrdinaryGet.

Scope. This text covers the core language (ECMA-262) and the engine and host mechanisms needed to reason about it. It does not cover the DOM, Web APIs, or frameworks, except where a host hook (HostEnqueuePromiseJob, HostLoadImportedModule, cross-origin isolation) is where a language-level behaviour is actually decided. Engine details describe V8 unless stated otherwise, with SpiderMonkey and JavaScriptCore noted where their designs differ instructively.