Skip to content

Chapter 5: JavaScript Standardized

In 1996, JavaScript was a Netscape feature. By 1997, it was an Ecma standard. The transition took less than two years, and the work that started then is still going.

That word — standard — does a lot of quiet work in this chapter. A standardized language is one where any conforming implementation is the language. Code written against ES2023 runs on V8, JavaScriptCore, SpiderMonkey, and any future engine that meets the spec, because all of them agree on what JavaScript is. None of those engines own the language. The standard does.

This is the institutional reality that makes the platform-first argument in the rest of this book defensible. We can write to the browser because the browser is committed, by treaty among the people who build the engines, to not break what it has already shipped. The treaty isn’t a contract anyone could sue over. It’s a thirty-year accumulation of decisions made by a committee that meets six times a year, in Ecma International conference rooms and (more often now) on Zoom calls, and votes on what JavaScript will become.

That committee is TC39, and the story of how it works is one of the great quiet successes of computing.

Ecma is a standards body in Geneva. The name once stood for European Computer Manufacturers Association; it has stood for nothing in particular since 1994, when the organization dropped its expansion in recognition that its membership had long since stopped being European or only-manufacturers. Ecma standardizes programming languages, file formats, and protocols, and publishes them under names like ECMA-262.

ECMA-262 is the JavaScript specification. The committee that writes it is called Technical Committee 39, or TC39. Membership is open to Ecma members, which means the major browser vendors (Google, Apple, Mozilla, Microsoft), large platform companies (Meta during the React years, Bloomberg, Igalia, Sony), framework and tooling makers when they choose to engage, and individual experts invited for specific work. Everyone in the room is paid by their employer to be there. The committee meets six times a year, with online working-group activity continuously in between.

The way changes happen is called the stage process. It explains why JavaScript moves the way it moves.

A proposal starts at Stage 0, where someone has written down an idea — a new operator, a new method, a new syntactic form — without any guarantee that anyone agrees with it. A Stage 0 proposal is essentially a public draft. Stage 1 means the committee has agreed the problem is worth solving, even if the solution might change. Stage 2 is a written draft of the actual spec text. Stage 3 is a candidate spec: the design is locked, and the committee is asking implementations to ship it so the proposal can collect real-world feedback. Stage 4 means it’s done — at least two implementations exist, the conformance test suite passes, and the proposal is ready for the next annual cut of the standard.

Most proposals die between Stage 0 and Stage 2. That’s the system working. The committee is conservative because the contract is for keeps: once a feature ships in browsers, it’s almost impossible to remove. The web does not break the web is the rule everyone in the room is working from, and the stage process is what makes that rule possible.

Anyone who has read an IETF RFC will recognize the rhythm. TC39’s process is institutionally close to rough consensus and running code — the same patience, the same refusal to ship anything that two implementations haven’t independently agreed on. The internet itself was built this way. Its application layer is being built the same way now.

Brendan Eich, the language’s original author, has been involved with TC39 in one capacity or another since the beginning. He co-founded Mozilla in 1998, served as Mozilla’s CTO, and has continued contributing to the language’s evolution long after stepping back from day-to-day Mozilla leadership. The chair role rotates; Eich’s contribution has more often been as one of the people in the room with thirty years of context about why a particular design was rejected in 2003 and why it still should be.

This is the institutional shape of how the language now grows. Slow, deliberate, distributed across companies that compete fiercely on everything else, and almost completely invisible to the working developer. Most readers will never have to know what stage a proposal is at. They’ll just notice, every June or so, that a few new methods have arrived.

The first three editions of ECMAScript came out quickly. ES1 in 1997, ES2 in 1998 (mostly editorial), ES3 in 1999. By the end of 1999, JavaScript had a real spec that browsers were largely conforming to. The committee turned its attention to ES4, and the language stopped moving for ten years.

ES4 was ambitious. The drafts that circulated through the early 2000s proposed classes, optional static typing, packages and namespaces, generics, getters and setters, multiple-dispatch overloading, and a number of more speculative features. The committee’s editor on the work was Waldemar Horwat, a former Netscape engineer who had taken over editorial duties after Eich. Much of the ES4 design was being prototyped in parallel as ActionScript 3, the language used in Macromedia’s (later Adobe’s) Flash Player. ActionScript 3 shipped in 2006 and ran ES4-style code in production at scale.

Microsoft, whose JScript implementation lived inside Internet Explorer, viewed the ES4 work as too large a change to ship into an existing platform. Yahoo’s representative, Douglas Crockford, agreed publicly. Crockford had spent the previous several years arguing that JavaScript was a serious language hidden inside a thicket of poor implementations and accidental complexity, and that the right response was to use less of the language — to identify the good parts and discipline yourself to stay inside them. ES4 was the opposite move. It was an attempt to add more language.

The factions hardened. Mozilla and Adobe were committed to ES4. Microsoft and Yahoo refused to implement it. The committee was deadlocked through most of 2007 and into 2008. The disagreement was not really about any single feature. It was about what kind of language JavaScript ought to be.

In August 2008, at a TC39 meeting in Oslo, the committee abandoned ES4. The agreed-on path forward was called Harmony. A smaller, incremental successor to ES3 (originally called ES3.1) would ship as the next edition, and the ES4 ambitions would be deferred, broken into smaller proposals, and reconsidered later. Eich’s announcement of the resolution was as gracious as the circumstance allowed. The ten-year stall was over. The language could move again.

It’s tempting, in hindsight, to call ES4 a failure. The more honest assessment is that the committee chose continuity over ambition, and the choice is the reason JavaScript can still claim to be the same language it was twenty years ago. Many of the features ES4 was reaching for did eventually arrive — classes in ES2015, getters and setters in ES5, modules in ES2015, static types (in their own way) in TypeScript. They arrived one at a time, through the stage process, with two or more implementations agreeing on each one. The Harmony decision was a vote for that pace.

It also closed the door on a parallel reality where the language might have grown a Microsoft fork or a Mozilla fork. The most important outcome of the ES4 standoff is the one that didn’t happen. JavaScript did not split.

ES5: The Language Gets a Spec It Can Trust

Section titled “ES5: The Language Gets a Spec It Can Trust”

ES5 was published in December 2009, ten years after ES3. The editor was Allen Wirfs-Brock, who had joined Microsoft a few years earlier after a long career in object-oriented language design — he had worked on Smalltalk at ParcPlace and on early object systems before that. The work he and the committee did over the following years produced the most rigorous spec JavaScript had ever had.

ES5 itself was modest by ES4 standards. The additions read like a checklist of repairs.

Array.prototype.forEach, map, filter, reduce, every, and some — the higher-order iteration methods that today feel native to the language but had to be added by hand or by library before ES5. Object.create, Object.defineProperty, Object.keys, and Object.freeze — a proper API for working with objects and their properties, including getters and setters. JSON.parse and JSON.stringify in the standard library, ratifying Crockford’s earlier proposal that JSON was the data interchange format the web had been quietly converging on. And strict mode, accessed by writing "use strict" at the top of a file or function, which turned off many of the language’s worst legacy behaviors: implicit globals, silently failing assignments to read-only properties, duplicate parameter names.

None of these features were dramatic. Collectively they made it possible to write modern JavaScript without the contortions that the language’s earlier design had required.

The deeper significance of ES5 was the spec itself. Wirfs-Brock continued as editor through ES5.1 (a small correction in 2011) and into ES6, and over those years he rewrote the specification’s prose into a much more precise form. The earlier ECMAScript specs were readable but informal. The post-Wirfs-Brock specs were closer to the rigor of an ISO language standard — algorithmic descriptions of every operation, precise abstract terminology, explicit edge-case handling. The change mattered because it gave implementers and tool authors a document they could trust completely.

The same year that the committee published ES5, Crockford’s JavaScript: The Good Parts finished its run as the most influential book about the language ever printed. Crockford’s argument — that the careful subset of the language was a serious tool — and the committee’s argument — that the careful subset would now be the standard — were two halves of the same shift. The field’s faith in JavaScript began to recover. Not all of the rehabilitation came from inside TC39, but a real share of it did.

ES5 didn’t make JavaScript a beautiful language. It made it a trustworthy one. Code written against ES5 in 2010 still runs in every browser shipping today. That continuity is what the rest of the language’s growth has been built on.

ES6 was renamed ES2015 during its development to reflect that the committee was moving to an annual release cadence. The renaming was forward-looking — ES2016 would follow a year later, and ES2017, and so on. In retrospect it was one of the most consequential procedural decisions TC39 has made. The release model that came with it is the reason JavaScript has been able to grow steadily rather than in decade-long pulses.

ES2015 itself is one of the largest version jumps in the history of any widely deployed language. The features that landed in a single June release included:

let and const, finally giving JavaScript block-scoped variable bindings and a way out of var’s function-scoped quirks. Arrow functions, with their lexical this, making functional-style callbacks much easier to write. Classes, as syntactic sugar over the prototype chain — not a new object model, but a readable way to write the one that had always been there. Promise as a built-in, ratifying the design that had been refined through years of library work in Bluebird, Q, and the various A+ implementations. Destructuring. Default parameters. Rest and spread. Template literals. Symbol as a new primitive type. Map, Set, WeakMap, and WeakSet as proper collection types. Generators (function* and yield), which would later make async/await implementable on top.

The most important addition, architecturally, was modules. Before ES2015, JavaScript had no native module system. The community had built several — CommonJS (Node’s choice), AMD (RequireJS), UMD (the lowest-common-denominator wrapper) — and bundlers like Browserify and Webpack existed in part to translate between them. ES2015 added import and export as language-level constructs. Browser support came slowly (Safari and Chrome in 2017–2018, Edge after the Chromium switch), but once it arrived, the language had its own native module system that didn’t depend on a bundler to interpret. This is the substrate the rest of the modern frontend ecosystem now sits on.

After ES2015, TC39 began shipping a new version every June. The release model is tight. Anything at Stage 4 by the cutoff makes the cut; everything else waits for next year. The annual versions are deliberately small. ES2016 added two things — Array.prototype.includes and the ** exponentiation operator. ES2017 added async/await, Object.values and Object.entries, and string padding. ES2018 added async iteration, Promise.prototype.finally, and object rest/spread. The pattern repeats: a handful of focused features per year, each of which has been through the full stage process and shipped in at least two engines before being ratified.

The cadence has the same shape as a well-run software team’s release cycle. Small batches, predictable timing, no heroic mega-releases. Anyone who has worked on a backend deployment pipeline will recognize the principle — large changes are expensive and risky, small changes compound. TC39 is running continuous delivery on a programming language.

The trade is that proposals stay in flight for years. Optional chaining (?.) was first proposed in 2017 and shipped in ES2020. Top-level await was proposed around the same time and landed in ES2022. The price of consensus is patience. The benefit is that the features that ship are the ones that survived the stage process.

In October 2012, Microsoft announced TypeScript. The lead designer was Anders Hejlsberg.

Hejlsberg’s career is one of the longest and most consequential in language design. At Borland in the 1980s he wrote Turbo Pascal, the compiler that taught a generation of programmers what a fast development loop felt like. In the 1990s he led Delphi, Borland’s object-oriented Pascal environment for Windows. In 1996 he moved to Microsoft and led the design of C#, which has been Microsoft’s flagship managed language ever since. He worked on F# alongside Don Syme. By 2012, when TypeScript was announced, Hejlsberg had spent thirty years thinking about how to add type safety to languages where developers wanted ergonomics.

TypeScript’s design choices show that experience.

It’s a superset of JavaScript. Every valid JavaScript program is a valid TypeScript program. The type annotations are an additional syntactic layer that the TypeScript compiler reads, type-checks, and then strips out, producing plain JavaScript. There’s no parallel runtime, no separate object model, no different module system. A TypeScript codebase ships JavaScript. The types exist at edit time and at compile time, and then they’re gone.

The type system is structural, not nominal. Two types are compatible if their shapes are compatible, regardless of where they were declared. An object with { name: string; id: number } satisfies any interface that requires { name: string }, whether or not the object’s author had ever heard of that interface. This is the right shape for JavaScript. The underlying language has always been duck-typed: code didn’t care what class an object came from, only what properties it exposed. TypeScript’s type system made that pattern explicit instead of fighting it.

And the types are gradual. A codebase can adopt TypeScript file by file, with any as the escape hatch for anything not yet annotated. Strictness is a dial — strict: true in tsconfig.json turns on the full set of checks, but a team can tighten gradually rather than all at once. Many large codebases lived for years with most files in any and only the critical paths tightly typed.

These three decisions — superset, structural, gradual — are why TypeScript won.

The most direct competition was Flow, released by Facebook in 2014 as a parallel static-type system for JavaScript. Flow had a sophisticated type inference engine and, for a time, was the default choice inside React codebases that wanted types. Flow lost. It lost partly for ecosystem reasons (TypeScript’s tooling, especially in editors, became significantly better), partly because Microsoft’s commitment to TypeScript was unambiguous while Facebook’s commitment to Flow waxed and waned, and partly because TypeScript’s structural-and-gradual design was simply a better fit for how JavaScript was being written. By around 2019, even React’s own internal teams were migrating to TypeScript. Flow remains in maintenance use inside Meta; outside Meta it’s effectively gone.

TypeScript itself has been quietly absorbed into how serious frontend codebases work. The 2024 State of JS survey put TypeScript usage above 75% among working JavaScript developers, with most of the rest reporting they wanted to be using it. Frameworks ship their public APIs as TypeScript definitions. Libraries publish .d.ts files alongside their JavaScript. New projects default to TypeScript without a discussion. The shift happened so steadily that there was never a moment of the JavaScript world has switched — it just had.

TypeScript matters for this book because it’s the canonical example of decoration rather than replacement. The compiler doesn’t substitute a parallel reality the way some frameworks do. It adds an opinion on top of the platform’s language and then steps out of the way at runtime. The output is the same JavaScript the engine would have run anyway. The platform’s contract is preserved; the developer ergonomics are improved.

That’s the model the rest of this book is going to argue for in other places.

Three things follow from the standardization story.

The first is that the language is evergreen. Code written against ES2015 will keep working. Code written against ES2024 will keep working. Browsers will not break a feature once it has shipped through TC39. That guarantee is what makes it possible to build applications that don’t need to be rewritten every three years to keep up with platform churn. It is also what makes it possible to ship code today that we expect to still run twenty years from now — including, for some readers of this book, code that AI tools are going to generate without human review.

The second is that there is one language. The ES4 standoff could have produced a Microsoft fork and a Mozilla fork, and JavaScript would now be in roughly the position C++ was in for decades — multiple dialects, each vendor implementing some subset, with portable code constrained to the intersection. Instead, the same code runs the same way on every modern engine. The cost of that uniformity was ten years of slow movement and one difficult committee decision in 2008. The cost was worth paying.

The third is harder to state without sounding sentimental. The work of TC39 and Ecma is one of the field’s clearest examples of institutions that work. Competing companies sit in the same room, on the same standing committee, and over twenty-five years have produced a language that is now the most widely deployed programming runtime on Earth. The committee’s failure modes are slow, conservative, and occasionally frustrating. Its successes are durable in a way that almost nothing else in the modern software stack is. The browser’s evergreen platform exists because TC39 — and the equivalent committees at the W3C and the WHATWG — have built it that way.

The next chapter is about the other side of that institutional history. The browser engines themselves, and what it took to get four of them to agree on anything at all.

The exercise for this chapter is to read something.

Pick a TC39 proposal currently at Stage 2 or Stage 3. The list lives at github.com/tc39/proposals. Pick one that catches your eye — something small enough that the proposal repo’s README will explain it in a few minutes. Read the README. Click through to the spec text. Look at the commit history to get a sense of how long the proposal has been in flight and how many revisions it has been through.

Then answer these:

  1. What problem is the proposal solving? Is it a problem the language has, or a problem a particular kind of application has?
  2. Is there a polyfill or library that already provides something similar? What does the proposal do that the library version can’t?
  3. Read the README’s “Champion” section. Who is sponsoring this work, and which Ecma member do they represent?
  4. Find the most recent committee meeting where this proposal was discussed (the tc39/notes repo has the minutes). What did the committee push back on? What did they agree?
  5. If this proposal eventually reaches Stage 4 and ships in browsers, will the polyfill or library still be needed? Or will the platform have absorbed the capability completely?

The goal isn’t to become a TC39 contributor. The goal is to see, with your own eyes, what the work of language standardization actually looks like — and to recognize, the next time a new JavaScript feature lands quietly in your browser, the years of patient committee work that produced it.