# Modern JavaScript (ES2020βES2025) β Deep Dive
> Comprehensive research on JavaScript features from ES2020 through ES2025.
---
## 1. ES2020 (ES11) Features
### Nullish Coalescing (`??`)
Returns right operand only when left is `null` or `undefined` β NOT falsy values like `0` or `""`.
```javascript
// β Logical OR β treats 0 and "" as falsy
const port = config.port || 3000; // 0 becomes 3000! β
// β
Nullish coalescing β only null/undefined
const port = config.port ?? 3000; // 0 stays 0 β
const name = config.name ?? "Anonymous"; // "" stays "" β
```
### Optional Chaining (`?.`)
Safe property access without intermediate checks.
```javascript
// β Before β verbose
const city = user && user.address && user.address.city;
// β
After β clean
const city = user?.address?.city;
// Safe function calls
const result = safeFunction?.();
// Safe array access
const first = arr?.[0];
// Short-circuit assignment
user?.preferences?.theme ??= "dark";
```
### `globalThis`
Universal global object reference β works in browser, Node.js, and Web Workers.
```javascript
// Before β platform-specific
const g = typeof window !== 'undefined' ? window
: typeof global !== 'undefined' ? global
: typeof self !== 'undefined' ? self : {};
// After β universal
console.log(globalThis); // window, global, or self depending on context
```
### `Promise.allSettled()`
Wait for all promises, regardless of success/failure.
```javascript
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${i} succeeded:`, result.value);
} else {
console.log(`Promise ${i} failed:`, result.reason);
}
});
// Use case: "Fire and forget all, collect results"
// vs Promise.all() β fails fast on first rejection
```
### `BigInt`
Arbitrary-precision integers β beyond `Number.MAX_SAFE_INTEGER` (2β΅Β³-1).
```javascript
const bigNum = 9007199254740991n; // n suffix
const bigNum2 = BigInt("9007199254740991");
// BigInt math
console.log(bigNum + bigNum2); // 18014398509481982n
// β οΈ Cannot mix with Number
// bigNum + 1 // TypeError!
bigNum + 1n // β
Number(bigNum) // Convert to number (precision loss possible)
```
---
## 2. ES2021 (ES12) Features
### `String.prototype.replaceAll()`
Replace all occurrences without regex.
```javascript
// β Before β need /g flag
"text".replace("t", "T"); // "Text" (first only)
"text".replace(/t/g, "T"); // "Text" (all)
// β
After β no regex needed
"text".replaceAll("t", "T"); // "Text"
```
### `Promise.any()`
Returns first *fulfilled* promise. Fails only if ALL reject.
```javascript
// Race β returns first settled (fulfilled OR rejected)
const winner = await Promise.race(promises);
// Any β returns first FULFILLED (waits out rejections)
const winner = await Promise.any(promises);
// AggregateError if all fail
try {
await Promise.any([Promise.reject(1), Promise.reject(2)]);
} catch (e) {
console.log(e.errors); // [1, 2] β AggregateError
}
// Use case: Try multiple CDN endpoints, use first that succeeds
const cdnUrl = await Promise.any([
fetch('https://cdn1.com/lib.js'),
fetch('https://cdn2.com/lib.js'),
fetch('https://cdn3.com/lib.js'),
]);
```
### `logicalAssignment` (??=, ||=, &&=)
```javascript
let config = { port: 0 };
config.port ||= 3000; // No-op, port is 0 (truthy-ish... wait, 0 is falsy)
// Actually: config.port ||= 3000 β port becomes 3000 (0 is falsy)
config.port ??= 3000; // No-op, port is 0 (not null/undefined) β
config.timeout ??= 5000; // Sets timeout to 5000
let arr = [];
arr &&= [...arr, "new"]; // Only push if arr exists
```
### `WeakRef` and `FinalizationRegistry`
```javascript
// WeakRef β doesn't prevent garbage collection
const ref = new WeakRef(largeObject);
const deref = ref.deref(); // Returns object or undefined if GC'd
// FinalizationRegistry β callback when object is collected
const registry = new FinalizationRegistry((heldValue) => {
console.log(`Object with key ${heldValue} was garbage collected`);
});
registry.register(largeObject, "myKey");
```
---
## 3. ES2022 (ES13) Features
### Top-Level `await`
Use `await` at module scope (in ES modules only).
```javascript
// β
In ES modules (.mjs or "type": "module" in package.json)
const users = await fetch('/api/users').then(r => r.json());
const config = await import('./config.js');
// Before β needed IIFE
const init = async () => {
const users = await fetch('/api/users').then(r => r.json());
};
init();
```
### `.at()` β Negative Indexing
Works on arrays, strings, and typed arrays.
```javascript
const arr = [10, 20, 30, 40, 50];
arr.at(0); // 10 (same as arr[0])
arr.at(-1); // 50 (last element)
arr.at(-2); // 40 (second to last)
"hello".at(-1); // "o"
Uint8Array.from([1,2,3]).at(-1); // 3
```
### Class Fields and Private Methods
```javascript
class Counter {
// Public field
count = 0;
// Private field (#)
#step = 1;
// Private method
#validate(n) {
return Number.isInteger(n) && n >= 0;
}
increment(n = 1) {
if (!this.#validate(n)) throw new TypeError();
this.count += n * this.#step;
}
// Private accessor
#get step() { return this.#step; }
#set step(v) { this.#step = v; }
}
```
### `Object.hasOwn()`
Safer alternative to `Object.prototype.hasOwnProperty.call()`.
```javascript
// β Verbose β fails if object has own hasOwnProperty
obj.hasOwnProperty('key');
// β
Safe β static method
Object.hasOwn(obj, 'key');
// Works even with null prototype
const map = Object.create(null);
Object.hasOwn(map, 'key'); // β
```
---
## 4. ES2023 (ES14) Features
### Array `.at()` was ES2022, new features:
### `Array.prototype.toSorted()`, `toReversed()`, `toSpliced()`, `with()`
Non-mutating array operations.
```javascript
const arr = [3, 1, 4, 1, 5];
// Mutating (original)
arr.sort(); // [1, 1, 3, 4, 5] β arr is changed
arr.reverse(); // arr is changed
arr.splice(1, 2); // arr is changed
arr[0] = 99; // arr is changed
// Non-mutating (new)
const sorted = arr.toSorted(); // New sorted array
const reversed = arr.toReversed(); // New reversed array
const spliced = arr.toSpliced(1, 2); // New array with elements removed
const updated = arr.with(0, 99); // New array with index 0 replaced
// Original arr unchanged β
```
### `Array.prototype.findLast()` and `findLastIndex()`
Search from the end.
```javascript
const nums = [10, 20, 30, 40, 50];
nums.findLast(n => n > 25); // 50
nums.findLastIndex(n => n > 25); // 4
nums.findLast(n => n % 10 === 0); // 50 (all match, returns last)
// Use case: Find last active item in a list
const lastActive = users.findLast(u => u.isActive);
```
### `Hashbang Grammar`
```javascript
#!/usr/bin/env node
// Now valid JavaScript syntax in ES modules
console.log("Hello");
```
---
## 5. ES2024 (ES15) Features
### `Array.prototype.toSpan()` (Staged)
### Regular Match Indices (`/d` Flag)
```javascript
const regex = /a(b)c/d; // /d flag captures group indices
const match = "abc".match(regex);
console.log(match.indices);
// [ [0, 3], [1, 2] ] // [full match indices, group 1 indices]
// Named group indices
const named = /(?<letter>x)/d;
"xyz".match(named).indices.groups;
// { letter: [0, 1] }
```
### `RegExp.v` Flag β Unicode Sets
```javascript
// More efficient Unicode matching, supports set operations
const regex = /[\p{Script=Han}&&[^\p{Emoji}]]/v;
```
### `Promise.withResolvers()`
Create a promise and its control functions separately.
```javascript
// Before β awkward pattern
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// use resolve/reject later...
// After β clean API
const { promise, resolve, reject } = Promise.withResolvers();
// Pass promise to consumer, resolve/reject to producer
apiConsumer(promise);
setTimeout(() => resolve(data), 1000);
// Use case: Event-to-promise bridge
function waitForEvent(target, type) {
const { promise, resolve } = Promise.withResolvers();
target.addEventListener(type, resolve, { once: true });
return promise;
}
const click = await waitForEvent(button, 'click');
```
### `SharedArrayBuffer` Atomic Operations
### `Temporal` API (Staged β not finalized)
Replacement for `Date` β timezone-aware, immutable date/time.
```javascript
// (Staged β not yet in browsers, but available via polyfill)
const now = Temporal.Now.zonedDateTimeISO('America/New_York');
const birthday = Temporal.ZonedDateTime.from({
year: 2000,
month: 1,
day: 15,
timeZone: 'America/New_York'
});
const age = now.year - birthday.year;
```
---
## 6. ES2025 (ES16) Features
### Array Grouping
```javascript
const people = [
{ name: 'Alice', dept: 'Engineering' },
{ name: 'Bob', dept: 'Marketing' },
{ name: 'Carol', dept: 'Engineering' },
{ name: 'Dave', dept: 'Marketing' },
];
// Group into Map
const byDept = Object.groupBy(people, p => p.dept);
// Map { 'Engineering' => [{...}, {...}], 'Marketing' => [{...}, {...}] }
// Group into Array (flat key-value pairs)
const byDeptArr = Object.groupByArray(people, p => p.dept);
// [ { key: 'Engineering', values: [...] }, { key: 'Marketing', values: [...] } ]
// Array.groupBy β returns array of groups
const grouped = people.toGrouped(p => p.dept);
```
### `Set` Methods: `.intersection()`, `.union()`, `.difference()`, `.symmetricDifference()`
```javascript
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
a.intersection(b); // Set { 3, 4 }
a.union(b); // Set { 1, 2, 3, 4, 5, 6 }
a.difference(b); // Set { 1, 2 }
a.symmetricDifference(b); // Set { 1, 2, 5, 6 }
// Also: isSubsetOf, isSupersetOf, isDisjointFrom
new Set([1, 2]).isSubsetOf(new Set([1, 2, 3])); // true
new Set([1, 2]).isDisjointFrom(new Set([3, 4])); // true
```
### `Map.groupBy()`
```javascript
const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
{ name: 'Carol', active: true },
];
const byStatus = Map.groupBy(users, u => u.active ? 'active' : 'inactive');
// Map { 'active' => [{...}, {...}], 'inactive' => [{...}] }
```
### `String.isWellFormed()` and `toWellFormed()`
```javascript
// Check for unpaired surrogates
"\uD800".isWellFormed(); // false
"Hello".isWellFormed(); // true
// Replace unpaired surrogates with U+FFFD
"\uD800 Hello".toWellFormed(); // "οΏ½ Hello"
```
### `ArrayBuffer.prototype.transfer()`
```javascript
// Grow/shrink ArrayBuffer without copying
const original = new ArrayBuffer(8);
const grown = original.transfer(16); // 16 bytes, original data preserved
// original is now detached (0 bytes)
```
---
## 7. Module Features
### Dynamic `import()`
```javascript
// Load module on demand
const module = await import('./heavy-module.js');
// Conditional loading
if (user.isPremium) {
const premiumFeatures = await import('./premium.js');
}
// Load multiple in parallel
const [react, reactDOM] = await Promise.all([
import('react'),
import('react-dom'),
]);
```
### Import Assertions / Attributes
```javascript
// Import JSON with assertion
import config from './config.json' assert { type: 'json' };
// New syntax (ES2023+)
import config from './config.json' with { type: 'json' };
```
### Top-Level Await (Recap)
```javascript
// In ES modules
const data = await fetch('/api/data.json').then(r => r.json());
export default data;
```
---
## Quick Reference: ES Feature Timeline
| Feature | Year | Use Case |
|---------|------|----------|
| `?.` Optional chaining | 2020 | Safe property access |
| `??` Nullish coalescing | 2020 | Null/undefined defaults |
| `globalThis` | 2020 | Universal global object |
| `Promise.allSettled()` | 2020 | Wait all, collect results |
| `BigInt` | 2020 | Large integers |
| `replaceAll()` | 2021 | Replace all without regex |
| `Promise.any()` | 2021 | First successful promise |
| `??=` / `||=` / `&&=` | 2021 | Assignment short-circuit |
| `WeakRef` | 2021 | GC-friendly references |
| Top-level `await` | 2022 | Async at module scope |
| `.at(-1)` | 2022 | Negative indexing |
| Private fields `#` | 2022 | True class privacy |
| `Object.hasOwn()` | 2022 | Safe hasOwnProperty |
| `.toSorted()` / `.with()` | 2023 | Non-mutating arrays |
| `.findLast()` | 2023 | Search from end |
| `Promise.withResolvers()` | 2024 | Separate promise controls |
| `RegExp /d` flag | 2024 | Match indices |
| `Set.union()` etc. | 2025 | Set operations |
| `Object.groupBy()` | 2025 | Group collections |