ECMAScript History & Evolution
JavaScript was created in 10 days in 1995 by Brendan Eich at Netscape. From that chaotic origin, it has evolved into the world's most widely deployed programming language. This is the full story of every significant version.
The Standards Body — TC39
TC39 (Technical Committee 39) is the ECMA International committee responsible for the ECMAScript specification. It operates on a stage-based proposal process:
| Stage | Name | Meaning |
|---|---|---|
| 0 | Strawperson | Initial idea, anyone can submit |
| 1 | Proposal | Champions assigned, problem/solution outlined |
| 2 | Draft | Formal spec text written |
| 3 | Candidate | Spec complete, implementations invited |
| 4 | Finished | Ready to be included in the next annual release |
Since ES2015, TC39 releases a new edition every year (typically June), even if it's a small release.
ES1 — ES3 (1997–1999): The Beginning
ES1 (1997)
The first standardised version. Based on JavaScript 1.1 from Netscape.
- Basic syntax, types, expressions, statements
typeof,instanceofoperators
ES2 (1998)
Minor editorial changes to align with ISO/IEC 16262. No new features.
ES3 (1999) — First major version
A huge improvement used as the baseline for years:
- Regular expressions
try/catch/finallyexception handlingswitch,do-while- String methods:
match(),replace(),search(),split() - Proper
undefined
ES3 became the compatibility target for IE6-8, which is why so many "legacy JS" patterns exist.
ES4 — Abandoned (2008)
ES4 was an ambitious proposal (classes, optional typing, packages, namespaces) driven by Macromedia/Adobe. Microsoft and others opposed it. The proposal was abandoned after years of committee deadlock.
The fallout led to a split:
- "ES3.1" — conservative improvements (became ES5)
- Harmony — a reset of bigger ideas (eventually became ES6)
ES5 (2009) — The Reliability Update
Used for nearly 6 years as the primary target. Key additions:
Strict Mode
javascript'use strict'; // Enables strict mode for the file or function // Prevents: undeclared variables, duplicate params, `this` as global, etc.
Array Methods
javascriptArray.prototype.forEach() Array.prototype.map() Array.prototype.filter() Array.prototype.reduce() / reduceRight() Array.prototype.some() / every() Array.prototype.indexOf() / lastIndexOf() Array.isArray()
Object Methods
javascriptObject.create() // prototypal inheritance Object.defineProperty() // property descriptors Object.keys() // enumerable own keys Object.freeze() // immutable objects Object.seal() Object.getPrototypeOf()
JSON
javascriptJSON.parse() JSON.stringify()
Function.prototype.bind()
javascriptconst bound = fn.bind(context, ...partialArgs);
ES5.1 (2011)
Editorial fixes. ES5.1 is what browsers actually shipped.
ES6 / ES2015 — The Revolution
The biggest update in JavaScript's history. Took ~6 years to standardise (2009–2015). Transformed the language.
let and const
javascriptlet mutable = 1; const IMMUTABLE = 2; // Block-scoped, TDZ, no re-declaration
Arrow Functions
javascriptconst add = (a, b) => a + b; // Lexical `this` — no own this binding
Template Literals
javascriptconst msg = `Hello, ${name}! You have ${count} messages.`; const multiline = ` line one line two `;
Destructuring
javascriptconst { name, age } = user; const [first, ...rest] = array;
Default Parameters
javascriptfunction greet(name = 'World') { return `Hello, ${name}!`; }
Rest & Spread
javascriptfunction sum(...nums) { return nums.reduce((a, b) => a + b, 0); } const merged = { ...obj1, ...obj2 }; const copy = [...arr];
Classes
javascriptclass Animal { constructor(name) { this.name = name; } speak() { return `${this.name} speaks.`; } } class Dog extends Animal { speak() { return `${this.name} barks.`; } }
Modules (ESM)
javascriptexport const PI = 3.14; export default function main() {} import PI, { helper } from './module.js';
Promises
javascriptfetch('/api').then(r => r.json()).catch(console.error);
Generators
javascriptfunction* gen() { yield 1; yield 2; yield 3; }
Symbols
javascriptconst id = Symbol('id');
Map, Set, WeakMap, WeakSet
javascriptconst map = new Map(); map.set(key, value); const set = new Set([1, 2, 3]);
for...of Loop
javascriptfor (const item of iterable) {}
Proxy & Reflect
Promise
String enhancements
javascript'hello'.startsWith('he') // true 'hello'.endsWith('lo') // true 'hello'.includes('ell') // true 'ha'.repeat(3) // "hahaha" '5'.padStart(3, '0') // "005"
Number enhancements
javascriptNumber.isInteger(42) // true Number.isFinite(Infinity) // false Number.isNaN(NaN) // true Number.parseInt() // same as global Number.EPSILON Number.MAX_SAFE_INTEGER
ES2016 (ES7) — Small but Meaningful
Array.prototype.includes()
javascript[1, 2, NaN].includes(NaN); // true — unlike indexOf
Exponentiation Operator
javascript2 ** 10; // 1024 — instead of Math.pow(2, 10) 2 **= 3; // compound assignment
ES2017 (ES8) — Async/Await
async / await
javascriptasync function fetchUser(id) { const res = await fetch(`/api/users/${id}`); return res.json(); }
Object.entries() / Object.values()
javascriptObject.entries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]] Object.values({ a: 1, b: 2 }); // [1, 2] Object.fromEntries([['a', 1]]); // { a: 1 } ← added ES2019
String Padding
javascript'5'.padStart(5, '0'); // "00005" 'hi'.padEnd(5, '.'); // "hi..."
Object.getOwnPropertyDescriptors()
Shared Memory & Atomics (SharedArrayBuffer)
Trailing Commas in Function Parameters
ES2018 (ES9) — Async Iteration
Rest/Spread for Objects
javascriptconst { a, ...rest } = { a: 1, b: 2, c: 3 }; // rest = { b: 2, c: 3 }
Async Iteration
javascriptfor await (const chunk of asyncIterable) {}
Promise.finally()
javascriptfetch('/api').then(use).catch(handle).finally(cleanup);
Named Capture Groups in Regex
javascriptconst match = '2025-06-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/); match.groups.year; // "2025" match.groups.month; // "06"
s (dotAll) Flag in Regex
javascript/hello.world/s.test('hello\nworld'); // true — . matches newline
ES2019 (ES10)
Array.prototype.flat() / flatMap()
javascript[1, [2, [3]]].flat(); // [1, 2, [3]] [1, [2, [3]]].flat(Infinity); // [1, 2, 3] [1, 2, 3].flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
Object.fromEntries()
javascriptconst obj = Object.fromEntries(map); // Map → Object const doubled = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * 2]));
String.prototype.trimStart() / trimEnd()
javascript' hi '.trimStart(); // "hi " ' hi '.trimEnd(); // " hi"
Optional Catch Binding
javascripttry { JSON.parse(x); } catch { /* no need to name the error */ }
Array.prototype.sort() guaranteed stable
Previously implementation-defined; now spec requires stable sort.
ES2020 (ES11)
Optional Chaining ?.
javascriptuser?.address?.city; // undefined if any link is null/undefined arr?.[0]; // safe array index fn?.(); // safe function call
Nullish Coalescing ??
javascriptconst name = user.name ?? 'Guest'; // only falls back on null/undefined const port = config.port ?? 3000;
??=, ||=, &&= (Logical Assignment — added ES2021)
BigInt
javascriptconst huge = 9007199254740993n; typeof huge; // "bigint"
Promise.allSettled()
javascriptconst results = await Promise.allSettled([p1, p2, p3]); // Always resolves; each result is { status: 'fulfilled'|'rejected', value|reason }
globalThis
javascript// Universal reference to the global object (window/global/self) globalThis.setTimeout(() => {}, 0);
Dynamic import()
javascriptconst module = await import('./module.js');
String.prototype.matchAll()
javascriptconst matches = [...'aabbcc'.matchAll(/([a-z])\1/g)];
ES2021 (ES12)
String.prototype.replaceAll()
javascript'a-b-c'.replaceAll('-', '_'); // "a_b_c"
Logical Assignment Operators
javascripta ||= b; // a = a || b a &&= b; // a = a && b a ??= b; // a = a ?? b
Promise.any()
javascript// Resolves with first fulfilled; rejects with AggregateError if all reject const first = await Promise.any([p1, p2, p3]);
Numeric Separators
javascriptconst million = 1_000_000; const hex = 0xFF_EC_D9_12; const bytes = 0b1010_0001;
WeakRef & FinalizationRegistry
ES2022 (ES13)
Class Fields — Public, Private, Static
javascriptclass Counter { count = 0; // public instance field #secret = 42; // private instance field static instances = 0; // static field static #id = 0; // private static field #increment() { this.count++; } // private method static create() { return new Counter(); } // static method }
Array.prototype.at()
javascript[1, 2, 3].at(-1); // 3 — negative indexing 'hello'.at(-1); // 'o'
Object.hasOwn()
javascriptObject.hasOwn(obj, 'prop'); // safer than obj.hasOwnProperty('prop')
Top-Level await
javascript// In ESM modules — no need for async wrapper const data = await fetch('/api').then(r => r.json()); export { data };
Error Cause
javascriptthrow new Error('Failed to load user', { cause: originalError }); err.cause; // the original error
Array.prototype.findLast() / findLastIndex()
ES2023 (ES14)
Array Methods on Copies (Non-mutating)
javascriptarr.toSorted(); // sorted copy (vs arr.sort() which mutates) arr.toReversed(); // reversed copy arr.toSpliced(1, 2, 'x'); // spliced copy arr.with(2, 'x'); // copy with index 2 replaced
Array.prototype.findLast() / findLastIndex() (formalised)
Hashbang (#!) Support for Scripts
javascript#!/usr/bin/env node console.log('Hello');
ES2024 (ES15)
Object.groupBy() / Map.groupBy()
javascriptconst grouped = Object.groupBy(users, user => user.role); // { admin: [...], user: [...] }
Promise.withResolvers()
javascriptconst { promise, resolve, reject } = Promise.withResolvers(); // Cleaner than: new Promise((resolve, reject) => { ... })
String.prototype.isWellFormed() / toWellFormed()
ArrayBuffer.prototype.transfer()
Atomics.waitAsync()
ES2025 (ES16) — In Progress
using — Explicit Resource Management
javascript{ using db = new DatabaseConnection(); await using file = await openFile('data.csv'); } // both automatically disposed
Iterator Helpers
javascriptIterator.from([1, 2, 3, 4, 5]) .filter(x => x % 2 === 0) .map(x => x * 10) .toArray(); // [20, 40]
RegExp.escape()
javascriptconst escaped = RegExp.escape('1 + 1 = 2'); // '1 \\+ 1 = 2' new RegExp(escaped); // safely use user input in regex
Set Methods
javascripta.union(b) a.intersection(b) a.difference(b) a.symmetricDifference(b) a.isSubsetOf(b) a.isSupersetOf(b)
ECMAScript Timeline Summary
| Version | Year | Headline Feature |
|---|---|---|
| ES1 | 1997 | First standard |
| ES3 | 1999 | Regex, try/catch |
| ES5 | 2009 | Strict mode, Array.map, JSON |
| ES6/ES2015 | 2015 | let/const, classes, modules, Promises, arrow fn, destructuring |
| ES2016 | 2016 | includes(), ** operator |
| ES2017 | 2017 | async/await, Object.entries() |
| ES2018 | 2018 | Object rest/spread, Promise.finally |
| ES2019 | 2019 | flat(), fromEntries(), optional catch |
| ES2020 | 2020 | ?., ??, BigInt, globalThis |
| ES2021 | 2021 | replaceAll(), logical assignment, Promise.any |
| ES2022 | 2022 | Private class fields, top-level await, at() |
| ES2023 | 2023 | Non-mutating array methods |
| ES2024 | 2024 | Object.groupBy(), Promise.withResolvers() |
| ES2025 | 2025 | using, Iterator helpers, Set methods |