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/constand 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/awaitObject.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/flatMapObject.fromEntries
ES2020 (ES11)
- Optional chaining
?. - Nullish coalescing
?? Promise.allSettledBigInt
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.withResolversObject.groupBy/Map.groupBy- Well-formed Unicode string checking (
isWellFormed,toWellFormed) - Regex
vflag (extended Unicode set operations)
ES2025 (ES16)
Iteratorhelper methods (.map,.filter,.takedirectly on iterators)- Native
Setalgebra: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 failsPromise.race— resolves/rejects as soon as one settlesPromise.allSettled— wait for all, regardless of outcomePromise.any— resolves as soon as one succeeds
Optional chaining ?. and nullish coalescing ??
user?.address?.cityshort-circuits toundefinedinstead of throwing ifuseroraddressis null/undefined.??returns the right side only if the left isnull/undefined— unlike||, which also triggers on0,'',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.