Modern JavaScript (ES6-ES2025)
Write clean, performant, maintainable JavaScript using modern language features. This skill covers ES6 through ES2025, emphasizing immutability, functional patterns, and expressive syntax.
Quick Decision Trees
"Which array method should I use?"
What do I need?
├─ Transform each element → .map()
├─ Keep some elements → .filter()
├─ Find one element → .find() / .findLast()
├─ Check if condition met → .some() / .every()
├─ Reduce to single value → .reduce()
├─ Get last element → .at(-1)
├─ Sort without mutating → .toSorted()
├─ Reverse without mutating → .toReversed()
├─ Group by property → Object.groupBy()
└─ Flatten nested arrays → .flat() / .flatMap()
"How do I handle nullish values?"
Nullish handling?
├─ Safe property access → obj?.prop / obj?.[key]
├─ Safe method call → obj?.method?.()
├─ Default for null/undefined only → value ?? 'default'
├─ Default for any falsy → value || 'default'
├─ Assign if null/undefined → obj.prop ??= 'default'
└─ Check property exists → Object.hasOwn(obj, 'key')
"Should I mutate or copy?"
Always prefer non-mutating methods:
├─ Sort array → .toSorted() (not .sort())
├─ Reverse array → .toReversed() (not .reverse())
├─ Splice array → .toSpliced() (not .splice())
├─ Update element → .with(i, val) (not arr[i] = val)
├─ Add to array → [...arr, item] (not .push())
└─ Merge objects → {...obj, key} (not Object.assign())
ES Version Quick Reference
| Version |
Year |
Key Features |
| ES6 |
2015 |
let/const, arrow functions, classes, destructuring, spread, Promises, modules, Symbol, Map/Set, Proxy, generators |
| ES2016 |
2016 |
Array.includes(), exponentiation operator ** |
| ES2017 |
2017 |
async/await, Object.values/entries, padStart/padEnd, trailing commas, SharedArrayBuffer, Atomics |
| ES2018 |
2018 |
Rest/spread for objects, for await...of, Promise.finally(), RegExp named groups, lookbehind, dotAll flag |
| ES2019 |
2019 |
.flat(), .flatMap(), Object.fromEntries(), trimStart/End(), optional catch binding, stable Array.sort() |
| ES2020 |
2020 |
Optional chaining ?., nullish coalescing ??, BigInt, Promise.allSettled(), globalThis, dynamic import() |
| ES2021 |
2021 |
String.replaceAll(), Promise.any(), logical assignment ??= and or=, numeric separators 1_000_000 |
| ES2022 |
2022 |
.at(), Object.hasOwn(), top-level await, private class fields #field, static blocks, Error.cause |
| ES2023 |
2023 |
.toSorted(), .toReversed(), .toSpliced(), .with(), .findLast(), .findLastIndex(), hashbang grammar |
| ES2024 |
2024 |
Object.groupBy(), Map.groupBy(), Promise.withResolvers(), RegExp v flag, resizable ArrayBuffer |
| ES2025 |
2025 |
Iterator helpers (.map, .filter, .take), Set methods (.union, .intersection), RegExp.escape(), using/await using |
Modernization Patterns
Array Access
// ❌ Legacy
const last = arr[arr.length - 1];
const secondLast = arr[arr.length - 2];
// ✅ Modern (ES2022)
const last = arr.at(-1);
const secondLast = arr.at(-2);
Non-Mutating Array Operations
// ❌ Mutates original array
const sorted = arr.sort((a, b) => a - b);
const reversed = arr.reverse();
// ✅ Returns new array (ES2023)
const sorted = arr.toSorted((a, b) => a - b);
const reversed = arr.toReversed();
const updated = arr.with(2, 'new value');
const removed = arr.toSpliced(1, 1);
String Replacement
// ❌ Legacy with regex
const result = str.replace(/foo/g, 'bar');
// ✅ Modern (ES2021)
const result = str.replaceAll('foo', 'bar');
Grouping Data
// ❌ Manual grouping
const grouped = items.reduce((acc, item) => {
const key = item.category;
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {});
// ✅ Modern (ES2024)
const grouped = Object.groupBy(items, item => item.category);
Nullish Handling
// ❌ Falsy check (0, '', false are valid values)
const value = input || 'default';
const name = user && user.profile && user.profile.name;
// ✅ Nullish check (only null/undefined)
const value = input ?? 'default';
const name = user?.profile?.name;
Property Existence
// ❌ Can be fooled by prototype or overwritten hasOwnProperty
if (obj.hasOwnProperty('key')) { }
// ✅ Modern (ES2022)
if (Object.hasOwn(obj, 'key')) { }
Logical Assignment
// ❌ Verbose assignment
if (obj.prop === null || obj.prop === undefined) {
obj.prop = 'default';
}
// ✅ Modern (ES2021)
obj.prop ??= 'default'; // Assign if null/undefined
obj.count ||= 0; // Assign if falsy
obj.enabled &&= check(); // Assign if truthy
Async Patterns
Promise Combinators
// Wait for all, fail if any fails
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);
// Wait for all, get status of each
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
// First to succeed
const fastest = await Promise.any([fetchFromCDN1(), fetchFromCDN2()]);
// First to settle
const winner = await Promise.race([fetchData(), timeout(5000)]);
Promise.withResolvers (ES2024)
// ❌ Legacy pattern
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// ✅ Modern (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();
Top-Level Await (ES2022)
// In ES modules, await at top level
const config = await fetch('/config.json').then(r => r.json());
const db = await connectDatabase(config);
export { db };
Functional Patterns
Immutable Object Updates
// Add/update property
const updated = { ...user, age: 31 };
// Remove property
const { password, ...userWithoutPassword } = user;
// Nested update
const updated = {
...state,
user: { ...state.user, name: 'New Name' }
};
Array Transformations
// Chain transformations (ES2023)
const result = users
.filter(u => u.active)
.map(u => u.name)
.toSorted();
// Using flatMap for filter+map (single pass)
const activeNames = users.flatMap(u => u.active ? [u.name] : []);
// ES2024: Group then process
const byStatus = Object.groupBy(users, u => u.active ? 'active' : 'inactive');
const activeNames = byStatus.active?.map(u => u.name) ?? [];
Composition
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const processUser = pipe(
user => ({ ...user, name: user.name.trim() }),
user => ({ ...user, email: user.email.toLowerCase() }),
user => ({ ...user, createdAt: new Date() })
);
Destructuring Patterns
Object Destructuring
// Basic with rename and default
const { name: userName, age = 18 } = user;
// Nested
const { address: { city, country } } = user;
// Rest
const { id, ...userData } = user;
Array Destructuring
// Skip elements
const [first, , third] = array;
// Rest
const [head, ...tail] = array;
// Swap variables
[a, b] = [b, a];
// Function returns
const [x, y] = getCoordinates();
Anti-Patterns
| Anti-Pattern |
Problem |
Modern Solution |
arr[arr.length-1] |
Verbose, error-prone |
arr.at(-1) |
.sort() on original |
Mutates array |
.toSorted() |
.replace(/g/) for all |
Regex overhead |
.replaceAll() |
obj.hasOwnProperty() |
Can be overwritten |
Object.hasOwn() |
value || default |
0, '', false treated as falsy |
value ?? default |
obj && obj.prop && obj.prop.method() |
Verbose null checks |
obj?.prop?.method?.() |
for (let i = 0; ...) |
Index bugs, verbose |
.map(), .filter(), for...of |
new Promise((res, rej) => ...) |
Boilerplate |
Promise.withResolvers() |
| Manual array grouping |
Verbose, error-prone |
Object.groupBy() |
Best Practices
- Use
const by default — Only use let when reassignment is needed
- Prefer arrow functions — Especially for callbacks and short functions
- Use template literals — Instead of string concatenation
- Destructure early — Extract what you need at function start
- Avoid mutations — Use
.toSorted(), .toReversed(), spread operator
- Use optional chaining — Prevent "Cannot read property of undefined"
- Use nullish coalescing —
?? for defaults, not || (unless intentional)
- Prefer array methods —
.map(), .filter(), .find() over loops
- Use
async/await — Instead of .then() chains
- Handle errors properly —
try/catch with async/await
Reference Documentation
ES Version References
| File |
Purpose |
| references/ES2016-ES2017.md |
includes, async/await, Object.values/entries, string padding |
| references/ES2018-ES2019.md |
rest/spread objects, flat/flatMap, RegExp named groups |
| references/ES2022-ES2023.md |
.at(), .toSorted(), .toReversed(), .findLast(), class features |
| references/ES2024.md |
Object.groupBy, Promise.withResolvers, RegExp v flag |
| references/ES2025.md |
Set methods, iterator helpers, using/await using |
| references/UPCOMING.md |
Temporal API, Decorators, Decorator Metadata |
Pattern References
| File |
Purpose |
| references/PROMISES.md |
Promise fundamentals, async/await, combinators |
| references/CONCURRENCY.md |
Parallel, batched, pool patterns, retry, cancellation |
| references/IMMUTABILITY.md |
Immutable data patterns, pure functions |
| references/COMPOSITION.md |
Higher-order functions, memoization, monads |
| references/CHEATSHEET.md |
Quick syntax reference |
Resources
Specifications
Documentation
Compatibility
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: modern-javascript3description: Proactively apply when creating web applications, Node.js services, or any JavaScript project. Triggers on JavaScript, ES6, ES2020, ES2022, ES2024, modern JS, refactor legacy, array methods, async/await, optional chaining, nullish coalescing, destructuring, spread, rest, template literals, arrow functions, toSorted, toReversed, at, groupBy, Promise, functional programming. Use when writing new JavaScript code, refactoring legacy code, modernizing codebases, implementing functional patterns, or reviewing JS for performance and readability. Modern JavaScript (ES6-ES2025) patterns and best practices. Use when this capability is needed.4---56# Modern JavaScript (ES6-ES2025)78Write clean, performant, maintainable JavaScript using modern language features. This skill covers ES6 through ES2025, emphasizing immutability, functional patterns, and expressive syntax.910## Quick Decision Trees1112### "Which array method should I use?"1314```15What do I need?16├─ Transform each element → .map()17├─ Keep some elements → .filter()18├─ Find one element → .find() / .findLast()19├─ Check if condition met → .some() / .every()20├─ Reduce to single value → .reduce()21├─ Get last element → .at(-1)22├─ Sort without mutating → .toSorted()23├─ Reverse without mutating → .toReversed()24├─ Group by property → Object.groupBy()25└─ Flatten nested arrays → .flat() / .flatMap()26```2728### "How do I handle nullish values?"2930```31Nullish handling?32├─ Safe property access → obj?.prop / obj?.[key]33├─ Safe method call → obj?.method?.()34├─ Default for null/undefined only → value ?? 'default'35├─ Default for any falsy → value || 'default'36├─ Assign if null/undefined → obj.prop ??= 'default'37└─ Check property exists → Object.hasOwn(obj, 'key')38```3940### "Should I mutate or copy?"4142```43Always prefer non-mutating methods:44├─ Sort array → .toSorted() (not .sort())45├─ Reverse array → .toReversed() (not .reverse())46├─ Splice array → .toSpliced() (not .splice())47├─ Update element → .with(i, val) (not arr[i] = val)48├─ Add to array → [...arr, item] (not .push())49└─ Merge objects → {...obj, key} (not Object.assign())50```5152## ES Version Quick Reference5354| Version | Year | Key Features |55|---------|------|--------------|56| ES6 | 2015 | let/const, arrow functions, classes, destructuring, spread, Promises, modules, Symbol, Map/Set, Proxy, generators |57| ES2016 | 2016 | Array.includes(), exponentiation operator ** |58| ES2017 | 2017 | async/await, Object.values/entries, padStart/padEnd, trailing commas, SharedArrayBuffer, Atomics |59| ES2018 | 2018 | Rest/spread for objects, for await...of, Promise.finally(), RegExp named groups, lookbehind, dotAll flag |60| ES2019 | 2019 | .flat(), .flatMap(), Object.fromEntries(), trimStart/End(), optional catch binding, stable Array.sort() |61| ES2020 | 2020 | Optional chaining ?., nullish coalescing ??, BigInt, Promise.allSettled(), globalThis, dynamic import() |62| ES2021 | 2021 | String.replaceAll(), Promise.any(), logical assignment ??= and or=, numeric separators 1_000_000 |63| ES2022 | 2022 | .at(), Object.hasOwn(), top-level await, private class fields #field, static blocks, Error.cause |64| ES2023 | 2023 | .toSorted(), .toReversed(), .toSpliced(), .with(), .findLast(), .findLastIndex(), hashbang grammar |65| ES2024 | 2024 | Object.groupBy(), Map.groupBy(), Promise.withResolvers(), RegExp v flag, resizable ArrayBuffer |66| ES2025 | 2025 | Iterator helpers (.map, .filter, .take), Set methods (.union, .intersection), RegExp.escape(), using/await using |6768## Modernization Patterns6970### Array Access7172```javascript73// ❌ Legacy74const last = arr[arr.length - 1];75const secondLast = arr[arr.length - 2];7677// ✅ Modern (ES2022)78const last = arr.at(-1);79const secondLast = arr.at(-2);80```8182### Non-Mutating Array Operations8384```javascript85// ❌ Mutates original array86const sorted = arr.sort((a, b) => a - b);87const reversed = arr.reverse();8889// ✅ Returns new array (ES2023)90const sorted = arr.toSorted((a, b) => a - b);91const reversed = arr.toReversed();92const updated = arr.with(2, 'new value');93const removed = arr.toSpliced(1, 1);94```9596### String Replacement9798```javascript99// ❌ Legacy with regex100const result = str.replace(/foo/g, 'bar');101102// ✅ Modern (ES2021)103const result = str.replaceAll('foo', 'bar');104```105106### Grouping Data107108```javascript109// ❌ Manual grouping110const grouped = items.reduce((acc, item) => {111 const key = item.category;112 acc[key] = acc[key] || [];113 acc[key].push(item);114 return acc;115}, {});116117// ✅ Modern (ES2024)118const grouped = Object.groupBy(items, item => item.category);119```120121### Nullish Handling122123```javascript124// ❌ Falsy check (0, '', false are valid values)125const value = input || 'default';126const name = user && user.profile && user.profile.name;127128// ✅ Nullish check (only null/undefined)129const value = input ?? 'default';130const name = user?.profile?.name;131```132133### Property Existence134135```javascript136// ❌ Can be fooled by prototype or overwritten hasOwnProperty137if (obj.hasOwnProperty('key')) { }138139// ✅ Modern (ES2022)140if (Object.hasOwn(obj, 'key')) { }141```142143### Logical Assignment144145```javascript146// ❌ Verbose assignment147if (obj.prop === null || obj.prop === undefined) {148 obj.prop = 'default';149}150151// ✅ Modern (ES2021)152obj.prop ??= 'default'; // Assign if null/undefined153obj.count ||= 0; // Assign if falsy154obj.enabled &&= check(); // Assign if truthy155```156157## Async Patterns158159### Promise Combinators160161```javascript162// Wait for all, fail if any fails163const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);164165// Wait for all, get status of each166const results = await Promise.allSettled([fetchA(), fetchB()]);167results.forEach(r => {168 if (r.status === 'fulfilled') console.log(r.value);169 else console.error(r.reason);170});171172// First to succeed173const fastest = await Promise.any([fetchFromCDN1(), fetchFromCDN2()]);174175// First to settle176const winner = await Promise.race([fetchData(), timeout(5000)]);177```178179### Promise.withResolvers (ES2024)180181```javascript182// ❌ Legacy pattern183let resolve, reject;184const promise = new Promise((res, rej) => {185 resolve = res;186 reject = rej;187});188189// ✅ Modern (ES2024)190const { promise, resolve, reject } = Promise.withResolvers();191```192193### Top-Level Await (ES2022)194195```javascript196// In ES modules, await at top level197const config = await fetch('/config.json').then(r => r.json());198const db = await connectDatabase(config);199200export { db };201```202203## Functional Patterns204205### Immutable Object Updates206207```javascript208// Add/update property209const updated = { ...user, age: 31 };210211// Remove property212const { password, ...userWithoutPassword } = user;213214// Nested update215const updated = {216 ...state,217 user: { ...state.user, name: 'New Name' }218};219```220221### Array Transformations222223```javascript224// Chain transformations (ES2023)225const result = users226 .filter(u => u.active)227 .map(u => u.name)228 .toSorted();229230// Using flatMap for filter+map (single pass)231const activeNames = users.flatMap(u => u.active ? [u.name] : []);232233// ES2024: Group then process234const byStatus = Object.groupBy(users, u => u.active ? 'active' : 'inactive');235const activeNames = byStatus.active?.map(u => u.name) ?? [];236```237238### Composition239240```javascript241const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);242const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);243244const processUser = pipe(245 user => ({ ...user, name: user.name.trim() }),246 user => ({ ...user, email: user.email.toLowerCase() }),247 user => ({ ...user, createdAt: new Date() })248);249```250251## Destructuring Patterns252253### Object Destructuring254255```javascript256// Basic with rename and default257const { name: userName, age = 18 } = user;258259// Nested260const { address: { city, country } } = user;261262// Rest263const { id, ...userData } = user;264```265266### Array Destructuring267268```javascript269// Skip elements270const [first, , third] = array;271272// Rest273const [head, ...tail] = array;274275// Swap variables276[a, b] = [b, a];277278// Function returns279const [x, y] = getCoordinates();280```281282## Anti-Patterns283284| Anti-Pattern | Problem | Modern Solution |285|--------------|---------|-----------------|286| `arr[arr.length-1]` | Verbose, error-prone | `arr.at(-1)` |287| `.sort()` on original | Mutates array | `.toSorted()` |288| `.replace(/g/)` for all | Regex overhead | `.replaceAll()` |289| `obj.hasOwnProperty()` | Can be overwritten | `Object.hasOwn()` |290| `value \|\| default` | 0, '', false treated as falsy | `value ?? default` |291| `obj && obj.prop && obj.prop.method()` | Verbose null checks | `obj?.prop?.method?.()` |292| `for (let i = 0; ...)` | Index bugs, verbose | `.map()`, `.filter()`, `for...of` |293| `new Promise((res, rej) => ...)` | Boilerplate | `Promise.withResolvers()` |294| Manual array grouping | Verbose, error-prone | `Object.groupBy()` |295296## Best Practices2972981. **Use `const` by default** — Only use `let` when reassignment is needed2992. **Prefer arrow functions** — Especially for callbacks and short functions3003. **Use template literals** — Instead of string concatenation3014. **Destructure early** — Extract what you need at function start3025. **Avoid mutations** — Use `.toSorted()`, `.toReversed()`, spread operator3036. **Use optional chaining** — Prevent "Cannot read property of undefined"3047. **Use nullish coalescing** — `??` for defaults, not `||` (unless intentional)3058. **Prefer array methods** — `.map()`, `.filter()`, `.find()` over loops3069. **Use `async/await`** — Instead of `.then()` chains30710. **Handle errors properly** — `try/catch` with async/await308309## Reference Documentation310311### ES Version References312| File | Purpose |313|------|---------|314| [references/ES2016-ES2017.md](references/ES2016-ES2017.md) | includes, async/await, Object.values/entries, string padding |315| [references/ES2018-ES2019.md](references/ES2018-ES2019.md) | rest/spread objects, flat/flatMap, RegExp named groups |316| [references/ES2022-ES2023.md](references/ES2022-ES2023.md) | .at(), .toSorted(), .toReversed(), .findLast(), class features |317| [references/ES2024.md](references/ES2024.md) | Object.groupBy, Promise.withResolvers, RegExp v flag |318| [references/ES2025.md](references/ES2025.md) | Set methods, iterator helpers, using/await using |319| [references/UPCOMING.md](references/UPCOMING.md) | Temporal API, Decorators, Decorator Metadata |320321### Pattern References322| File | Purpose |323|------|---------|324| [references/PROMISES.md](references/PROMISES.md) | Promise fundamentals, async/await, combinators |325| [references/CONCURRENCY.md](references/CONCURRENCY.md) | Parallel, batched, pool patterns, retry, cancellation |326| [references/IMMUTABILITY.md](references/IMMUTABILITY.md) | Immutable data patterns, pure functions |327| [references/COMPOSITION.md](references/COMPOSITION.md) | Higher-order functions, memoization, monads |328| [references/CHEATSHEET.md](references/CHEATSHEET.md) | Quick syntax reference329330## Resources331332### Specifications333- **ECMAScript Specification**: https://tc39.es/ecma262/ (living standard)334- **TC39 Proposals**: https://github.com/tc39/proposals (upcoming features)335- **TC39 Process**: https://tc39.es/process-document/ (how features are added)336337### Documentation338- **MDN Web Docs**: https://developer.mozilla.org/en-US/docs/Web/JavaScript339- **JavaScript.info**: https://javascript.info/340341### Compatibility342- **Can I Use**: https://caniuse.com (browser support tables)343- **Node.js ES Compatibility**: https://node.green/344345---346> Converted and distributed by [TomeVault](https://tomevault.io/claim/ccheney) — claim your Tome and manage your conversions.347<!-- tomevault:4.0:skill_md:2026-04-11 -->