Modern JavaScript
Use current JavaScript without confusing specification status, runtime support, and toolchain support. Treat these as three separate questions:
- Is it standardized? Check the frozen annual ECMA-262 edition or TC39's current proposal tracker.
- Does the target run it? Check the project's declared Node/browser targets and current compatibility data.
- Can tooling bridge the gap? Syntax may need a transform; built-ins need a polyfill. One does not imply the other.
Workflow
- Inspect
package.json(engines,browserslist, dependencies), runtime/deployment config,tsconfig.json, and Babel/bundler config. Do not invent a baseline. - Select the simplest standardized feature supported by that baseline.
- For a newer standard or mature proposal, state the required runtime, transform, or polyfill in the implementation or handoff.
- Preserve observable behavior while modernizing: mutation, evaluation order, iterator consumption, concurrency, cancellation, property descriptors, and error propagation matter more than shorter syntax.
- Run the project's formatter, type-checker, linter, and tests. Exercise both the native path and compatibility path when both ship.
Read references/COMPATIBILITY.md whenever support is uncertain, the feature is newer than ES2023, or a proposal is involved.
Status Rules
- Use the frozen ECMAScript 2026 specification for ES2026 attribution and the living draft for already-integrated future features.
- Use TC39's finished-proposals table for Stage 4 publication-year mapping and its active tracker for current stages.
- Do not infer an edition from the year a browser shipped a feature.
Array.fromAsync, for example, shipped before its ES2026 publication. - Do not describe Stage 3 or 2.7 syntax as standard JavaScript. It can still be a sound project choice when a maintained toolchain implements the selected proposal revision.
- Do not emit withdrawn Records & Tuples syntax (
#{}/#[]).
Edition Map
This is a routing index, not a substitute for the detailed references.
| Edition | Representative additions |
|---|---|
| ES2015 | let/const, classes, modules, arrow functions, destructuring, promises, generators, Map/Set, Proxy |
| ES2016 | exponentiation, Array.prototype.includes |
| ES2017 | async functions, Object.values/entries, string padding, shared memory and Atomics |
| ES2018 | object rest/spread, async iteration, Promise.prototype.finally, major RegExp additions |
| ES2019 | flat/flatMap, Object.fromEntries, optional catch binding, stable sort |
| ES2020 | optional chaining, nullish coalescing, BigInt, Promise.allSettled, dynamic import(), globalThis |
| ES2021 | logical assignment, Promise.any, replaceAll, numeric separators, WeakRef |
| ES2022 | .at, Object.hasOwn, top-level await, class fields/private elements/static blocks, error cause, RegExp indices |
| ES2023 | array change-by-copy methods, findLast/findLastIndex, hashbang grammar, symbols as weak keys |
| ES2024 | grouping, Promise.withResolvers, well-formed strings, RegExp v, resizable/transferable buffers, Atomics.waitAsync |
| ES2025 | Set methods, sync iterator helpers, RegExp.escape, Promise.try, import attributes/JSON modules, RegExp modifiers, Float16 |
| ES2026 | Array.fromAsync, Error.isError, Math.sumPrecise, Uint8Array base64/hex, Iterator.concat, Map/WeakMap upsert, JSON source access |
Already Stage 4 with publication expected in ES2027: Temporal, explicit resource management (using / await using), Atomics.pause, and Iterator.zip / zipKeyed. See references/UPCOMING.md.
Choose by Semantics
Collections
| Need | Prefer | Important behavior |
|---|---|---|
| Transform/filter an array | map, filter, flatMap | Eager; creates arrays |
| Process a large/infinite sync source | iterator helpers | Lazy; consumes the iterator |
| Find from the end | findLast, findLastIndex | Does not reverse or copy |
| Change without mutating the source | toSorted, toReversed, toSpliced, with | Shallow copy |
| Group under string/symbol keys | Object.groupBy | Returns a null-prototype object |
| Group under identity keys | Map.groupBy | Preserves key identity |
| Collect an async iterable | Array.fromAsync | Pulls and awaits sequentially |
| Run already-created work concurrently | Promise.all | Rejects early; does not cancel inputs |
Do not replace mutation mechanically. A private, newly allocated array can be mutated efficiently without exposing mutable shared state. Use change-by-copy methods when preserving the source is part of the contract.
const ranked = scores.toSorted((a, b) => b.points - a.points);
const byTeam = Object.groupBy(players, player => player.team);
if (Object.hasOwn(byTeam, requestedTeam)) {
render(byTeam[requestedTeam]);
}
Nullish and Property Checks
const city = user?.address?.city ?? 'Unknown';
// `??` preserves 0, false, and ''. Use `||` only when all falsy values mean absent.
settings.timeout ??= 5_000;
// Safe for overwritten or null-prototype objects.
if (Object.hasOwn(record, 'id')) use(record.id);
Async Work
// Independent work: concurrent.
const [user, permissions] = await Promise.all([
fetchUser(id),
fetchPermissions(id),
]);
// Ordered source: sequential consumption.
const pages = await Array.fromAsync(fetchPages());
// A loop is correct when each step depends on the previous one.
for (const migration of migrations) {
await migration.run();
}
Never use forEach(async () => ...) when completion must be awaited. Read references/PROMISES.md for combinators and references/CONCURRENCY.md for bounded concurrency and cancellation.
Modules
Use with for import attributes; the older assert spelling is not current ECMAScript.
import config from './config.json' with { type: 'json' };
const locale = await import(`./locales/${language}.js`);
Import attributes and JSON modules are ES2025, but module loading remains host-sensitive. Verify Node, browser, bundler, and test-runner support together.
Exact API Boundaries
structuredClone,fetch, streams,AbortController, and DOM APIs are host/web APIs, not ECMA-262 language features.Intl.*is standardized in ECMA-402, not ECMA-262.- TypeScript syntax and types are not JavaScript runtime features.
- A TypeScript
libentry supplies type declarations, not a runtime implementation. - Babel syntax transforms do not automatically install missing globals or prototype methods.
Compatibility Paths
Prefer native support when the target has it. Otherwise choose deliberately:
| Feature kind | Compatibility mechanism |
|---|---|
| Syntax (using, decorators) | Babel/TypeScript transform that matches the selected proposal revision |
| Built-in (Iterator.concat, Map#getOrInsert) | Runtime upgrade or maintained polyfill such as current core-js |
| Namespace API (Temporal) | Runtime upgrade or a maintained Temporal polyfill |
| Shared-memory primitive (Atomics.pause) | Native support only; feature-detect and keep a fallback algorithm |
TypeScript 7.0 is released, but it is not a polyfill and 7.0 has no stable programmatic compiler API. Projects whose framework/linter embeds TypeScript may need TypeScript 6 alongside it. See references/COMPATIBILITY.md.
Review Traps
.sort(),.reverse(),.splice(),Map#set,Set#add, and most TypedArray methods mutate. That is not automatically wrong; make it intentional.- Object/array spread is shallow and invokes ordinary property access; it does not preserve prototypes or descriptors.
structuredCloneis a host API and cannot clone functions, weak collections, or every platform object.Object.groupByhas no inheritedhasOwnPropertyortoString.- Iterator helpers are lazy and single-pass; arrays are reusable and eager.
Promise.alldoes not cancel remaining operations after rejection. PassAbortSignalwhen cancellation is required.Math.sumPreciseaccurately sums the binary Number inputs; it does not make decimal money exact.Error.isErroris cross-realm-safe for ECMAScript Error objects; host error types can have runtime-specific behavior.Temporalis not part of ES2026. Its Stage 4 publication target is ES2027.- Standard decorators are not legacy TypeScript
experimentalDecorators.
References
Load only what the task needs.
| Reference | Use for |
|---|---|
| ES2016-ES2017.md | early annual additions, async functions, shared memory |
| ES2018-ES2019.md | async iteration, object spread, RegExp, flattening |
| ES2022-ES2023.md | classes, .at, change-by-copy, weak symbol keys |
| ES2024.md | grouping, resolvers, Unicode sets, buffers |
| ES2025.md | Set/iterator helpers, modules, RegExp, Float16 |
| ES2026.md | all seven finalized ES2026 feature proposals |
| UPCOMING.md | Stage 4 ES2027 set, selected mature proposals, withdrawn syntax |
| COMPATIBILITY.md | runtime matrices, Babel/TypeScript/core-js/polyfill decisions |
| PROMISES.md | promise semantics and anti-patterns |
| CONCURRENCY.md | pools, retries, cancellation, async iteration |
| CHEATSHEET.md | compact syntax lookup |
Authority Order
- Frozen annual ECMA-262 editions for edition membership
- Living ECMA-262 plus TC39 proposal tracker/spec repositories for future status and semantics
- Test262 for conformance cases
- MDN Browser Compatibility Data and official engine/runtime releases for availability
- Official TypeScript, Babel, and polyfill documentation for compatibility paths