JavaScript History & Modern Features

A reference timeline of JavaScript/ECMAScript versions, from creation to today, plus a categorized breakdown of the features that matter most for day-to-day development.


Origins

1995 — Created by Brendan Eich at Netscape Built in 10 days. Originally called Mocha, then LiveScript, then renamed JavaScript as a marketing move to ride Java’s popularity — the two languages are otherwise unrelated.

1996 — Submitted to ECMA for standardization This is why the official spec name is “ECMAScript” (ES) and “JavaScript” is technically a trademarked implementation of that spec.


Version Timeline

ES1 (1997)

First standardized version.

ES2 (1998)

Minor editorial changes to align with an ISO standard.

ES3 (1999)

First real feature version: regular expressions, try/catch, switch statements, better string handling. This became the baseline browsers implemented for years.

ES4 — Abandoned (2003–2008)

An ambitious rewrite aiming for static typing, classes, modules, and namespaces. Caused a years-long conflict in the standards committee (Netscape/Mozilla vs. Microsoft/Yahoo) over how radical the language should become. Eventually scrapped entirely.

ES5 (2009)

The pragmatic compromise that emerged from the ES4 fallout. Added:

  • Strict mode
  • Native JSON support
  • Array methods: map, filter, forEach, reduce
  • Object.create, getters/setters, Object.defineProperty

Dominant version of JS for years; still a common compilation target for tooling.

ES6 / ES2015 — The Big Rebuild

The most significant single update in JS history. Also the point where the committee switched to yearly releases (hence “ES2015” naming going forward, though “ES6” is still commonly used).

Key additions:

  • let / const and block scoping
  • Arrow functions (lexical this)
  • Classes (basic syntax — no private fields yet)
  • Template literals
  • Destructuring (objects and arrays)
  • Default parameters
  • Spread/rest (initially for arrays and function args)
  • Promises
  • Modules (import / export)
  • Map, Set, WeakMap, WeakSet
  • Generators

ES2016 (ES7)

  • Array.prototype.includes
  • Exponentiation operator **

ES2017 (ES8)

  • async / await
  • Object.entries / Object.values

ES2018 (ES9)

  • Object spread/rest ({...obj}) — array spread was ES6, object spread came separately
  • Async iteration (for await...of)
  • Promise.finally

ES2019 (ES10)

  • Array.flat / flatMap
  • Object.fromEntries

ES2020 (ES11)

  • Optional chaining ?.
  • Nullish coalescing ??
  • Promise.allSettled
  • BigInt

ES2021 (ES12)

  • Promise.any
  • Logical assignment operators: ||=, &&=, ??=

ES2022 (ES13)

  • Class private fields (#field) and private methods
  • Top-level await
  • Array.at()

ES2023 (ES14)

  • Non-mutating array methods: toSorted, toReversed, toSpliced, with
  • Array.findLast / findLastIndex

ES2024 (ES15)

  • Promise.withResolvers
  • Object.groupBy / Map.groupBy
  • Well-formed Unicode string checking (isWellFormed, toWellFormed)
  • Regex v flag (extended Unicode set operations)

ES2025 (ES16)

  • Iterator helper methods (.map, .filter, .take directly on iterators)
  • Native Set algebra: union, intersection, difference, etc.
  • RegExp.escape
  • Import attributes: import data from "./file.json" with { type: "json" }

Where it’s heading

The committee runs on a rolling annual cadence with a formal proposal process (Stage 0 to Stage 4), so upcoming features can be tracked before they land. Things like the pipeline operator |> and pattern matching are still working through that process.

The throughline: 1995–2009 was JS finding its footing and nearly fracturing over ambition (ES4). 2015 was the reset that gave it a real foundation. Everything since has been steady, incremental refinement rather than another risky overhaul.


Modern Features by Category

Variable declarations & scoping

let / const replaced var because var is function-scoped (and hoisted in a confusing way), while let/const are block-scoped — they only exist inside the {} they’re declared in. const doesn’t mean immutable; it means the binding can’t be reassigned. Objects/arrays held in a const can still be mutated.

Arrow functions

(a, b) => a + b — the key behavioral difference from regular functions is that arrow functions don’t have their own this; they inherit it lexically from the enclosing scope. This solved the classic “this is undefined inside my callback” problem from pre-ES6 code.

Destructuring

Pulling values out of objects/arrays by shape:

const { name, age } = user;
const [first, second] = arr;

Also works in function parameters — useful for functions that take an options object.

Spread / rest (...)

Same syntax, opposite direction.

  • Spread expands: [...arr1, ...arr2], {...obj, extra: 1}
  • Rest collects: function f(first, ...rest) {}

This is how immutable-style state updates became easy: {...state, updatedField: x} instead of manually cloning.

Template literals

Backticks with ${} interpolation, spanning multiple lines natively. Also enables “tagged templates” (used by libraries like styled-components).

Classes

Syntactic sugar over prototypal inheritance: constructor, extends, super. Since ES2022, also supports private fields (#field) — true encapsulation, not just underscore convention.

Promises & async/await

Promises represent “a value that will exist eventually,” with three states: pending, fulfilled, rejected. async/await is sugar on top of promises that lets asynchronous code read like synchronous code — await pauses the function (not the thread) until the promise resolves.

Key coordination methods:

  • Promise.all — wait for all, fail if any fails
  • Promise.race — resolves/rejects as soon as one settles
  • Promise.allSettled — wait for all, regardless of outcome
  • Promise.any — resolves as soon as one succeeds

Optional chaining ?. and nullish coalescing ??

  • user?.address?.city short-circuits to undefined instead of throwing if user or address is null/undefined.
  • ?? returns the right side only if the left is null/undefined — unlike ||, which also triggers on 0, '', false.

Map, Set, WeakMap, WeakSet

  • Map — proper key-value store; keys can be any type (unlike object keys, which coerce to strings); preserves insertion order.
  • Set — stores unique values.
  • WeakMap/WeakSet — allow garbage collection of keys; useful for caches/metadata that shouldn’t leak memory.

Generators & iterators

function* and yield let a function pause and resume, producing a sequence of values over time. These underpin how for...of and async iteration work internally.

Modules

ESM (import/export) is statically analyzable (imports resolved at parse time, enabling tree-shaking) and always runs in strict mode automatically — unlike CommonJS (require). Top-level await works directly inside ESM modules.

Proxy & Reflect

Proxy lets you intercept operations on an object (get, set, delete). This is how Vue 3’s reactivity system and various validation/logging libraries work internally.