Advanced Destructuring
Extract values from arrays and objects with powerful patterns.
// Nested object destructuring
const user = {
name: "John",
address: {
city: "New York",
country: "USA",
coords: { lat: 40.7, lng: -74.0 }
},
contacts: {
email: "john@example.com",
phone: "123-456-7890"
}
};
const {
name,
address: { city, coords: { lat, lng } },
contacts: { email }
} = user;
console.log(city, lat, lng, email);
// "New York", 40.7, -74.0, "john@example.com"
// Default values with renaming
const { name: userName = "Anonymous", age = 25 } = user;
// Array destructuring with skipping
const colors = ["red", "green", "blue", "yellow"];
const [primary, , tertiary] = colors;
console.log(primary, tertiary); // "red", "blue"
// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1
// Rest in destructuring
const [first, second, ...remaining] = colors;
console.log(remaining); // ["blue", "yellow"]
const { name: n, ...otherProps } = user;
console.log(otherProps); // { address: {...}, contacts: {...} }
// Function parameter destructuring
function createUser({ name, email, role = "user" }) {
return { name, email, role, createdAt: Date.now() };
}
createUser({ name: "Jane", email: "jane@example.com" });
// Complex destructuring in function
function processOrder({
id,
items = [],
customer: { name, address: { city } = {} } = {}
} = {}) {
console.log(id, name, city, items.length);
}
// Destructuring in loops
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
for (const { id, name } of users) {
console.log(`${id}: ${name}`);
}
Spread and Rest Operators
Use ... for spreading and collecting values.
// Array spread
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// Clone array
const clone = [...arr1];
// Insert in middle
const withInsert = [0, ...arr1, 4, 5]; // [0, 1, 2, 3, 4, 5]
// Object spread
const defaults = { theme: "light", language: "en" };
const userPrefs = { theme: "dark" };
const settings = { ...defaults, ...userPrefs };
// { theme: "dark", language: "en" }
// Clone with modifications
const updatedUser = { ...user, name: "Jane", age: 26 };
// Shallow clone (nested objects are references!)
const original = { nested: { value: 1 } };
const copy = { ...original };
copy.nested.value = 2;
console.log(original.nested.value); // 2 (modified!)
// Deep clone
const deepClone = JSON.parse(JSON.stringify(original));
// Or: structuredClone(original) (modern browsers)
// Rest parameters (collect remaining arguments)
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function logFirst(first, ...rest) {
console.log("First:", first);
console.log("Rest:", rest);
}
logFirst(1, 2, 3, 4);
// First: 1
// Rest: [2, 3, 4]
// Spread in function calls
const numbers = [5, 10, 15];
console.log(Math.max(...numbers)); // 15
// Converting iterables
const str = "hello";
const chars = [...str]; // ["h", "e", "l", "l", "o"]
const set = new Set([1, 2, 3]);
const setArray = [...set]; // [1, 2, 3]
// Practical: Merge arrays with deduplication
const merged = [...new Set([...arr1, ...arr2])];
Optional Chaining & Nullish Coalescing
Handle undefined/null values safely and elegantly.
// Optional chaining (?.)
const user = {
name: "John",
address: null
};
// Without optional chaining
const city = user && user.address && user.address.city;
// With optional chaining
const cityOptional = user?.address?.city;
console.log(cityOptional); // undefined (no error!)
// With arrays
const users = null;
const firstUser = users?.[0];
const firstName = users?.[0]?.name;
// With methods
const result = user.getName?.();
const length = user.items?.length;
// With function calls
const callback = null;
callback?.(); // Does nothing, no error
// Nullish coalescing (??)
// Returns right side only if left is null or undefined
const value = null ?? "default"; // "default"
const value2 = undefined ?? "default"; // "default"
const value3 = 0 ?? "default"; // 0 (0 is NOT nullish!)
const value4 = "" ?? "default"; // "" (empty string is NOT nullish!)
const value5 = false ?? "default"; // false (false is NOT nullish!)
// Compare with OR (||)
// || returns right side for ANY falsy value
const orValue = 0 || "default"; // "default" (0 is falsy)
const orValue2 = "" || "default"; // "default" (empty string is falsy)
// Practical example
function getConfig(options = {}) {
return {
port: options.port ?? 3000,
host: options.host ?? "localhost",
debug: options.debug ?? false
};
}
getConfig({ port: 8080 });
// { port: 8080, host: "localhost", debug: false }
// Combining both
const settings = user?.settings?.theme ?? "light";
// Assignment operators (ES2021)
let config = null;
config ??= { default: true }; // Assign if null/undefined
let count = 0;
count ||= 10; // Assign if falsy (count stays 0? No, becomes 10!)
count &&= 5; // Assign if truthy
Use ?? when you only want to check for null/undefined. Use || when you want to check for any falsy value (0, "", false, null, undefined).
Symbols & Iterators
Create unique identifiers and custom iterable objects.
// Symbols - unique identifiers
const sym1 = Symbol("description");
const sym2 = Symbol("description");
console.log(sym1 === sym2); // false (always unique)
// Use as object keys
const ID = Symbol("id");
const user = {
[ID]: 123,
name: "John"
};
console.log(user[ID]); // 123
console.log(Object.keys(user)); // ["name"] - symbols are hidden
// Well-known symbols
class Collection {
constructor(items) {
this.items = items;
}
// Make it iterable
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { done: true };
}
};
}
// Custom string representation
[Symbol.toStringTag] = "Collection";
}
const collection = new Collection([1, 2, 3]);
for (const item of collection) {
console.log(item); // 1, 2, 3
}
console.log(collection.toString()); // "[object Collection]"
// Generator functions (easier iterators)
function* numberGenerator(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const gen = numberGenerator(1, 5);
console.log(gen.next()); // { value: 1, done: false }
console.log([...gen]); // [2, 3, 4, 5] (remaining values)
// Infinite generator
function* infiniteSequence() {
let n = 0;
while (true) {
yield n++;
}
}
const infinite = infiniteSequence();
console.log(infinite.next().value); // 0
console.log(infinite.next().value); // 1
// ...
// Practical: Paginated data
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (const page of paginate(data, 3)) {
console.log("Page:", page);
}
// Page: [1, 2, 3]
// Page: [4, 5, 6]
// Page: [7, 8, 9]
// Page: [10]
Map and Set
Specialized collections for key-value pairs and unique values.
// Map - key-value pairs with any key type
const map = new Map();
// Any value can be a key
map.set("string", "value1");
map.set(42, "value2");
map.set({ id: 1 }, "value3");
const objKey = { id: 2 };
map.set(objKey, "value4");
console.log(map.get("string")); // "value1"
console.log(map.get(objKey)); // "value4"
console.log(map.has(42)); // true
console.log(map.size); // 4
map.delete("string");
// map.clear() - remove all
// Initialize with entries
const userMap = new Map([
["user1", { name: "Alice" }],
["user2", { name: "Bob" }]
]);
// Iterating
for (const [key, value] of map) {
console.log(key, value);
}
map.forEach((value, key) => {
console.log(key, value);
});
console.log([...map.keys()]); // All keys
console.log([...map.values()]); // All values
console.log([...map.entries()]); // All [key, value] pairs
// Set - unique values only
const set = new Set([1, 2, 3, 3, 3]);
console.log(set.size); // 3 (duplicates removed)
set.add(4);
set.add(1); // Already exists, ignored
set.delete(2);
set.has(3); // true
// Array deduplication
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]
// Set operations
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...setA, ...setB]);
// {1, 2, 3, 4, 5, 6}
// Intersection
const intersection = new Set([...setA].filter(x => setB.has(x)));
// {3, 4}
// Difference
const difference = new Set([...setA].filter(x => !setB.has(x)));
// {1, 2}
// WeakMap & WeakSet (garbage-collectible keys)
const weakMap = new WeakMap();
let obj = { data: "important" };
weakMap.set(obj, "metadata");
obj = null; // Object can be garbage collected
// Entry in weakMap is also removed
Modern Features (ES2020-2023)
// BigInt (ES2020) - arbitrary precision integers
const bigNumber = 9007199254740991n; // Note the 'n'
const bigNumber2 = BigInt("9007199254740991");
console.log(bigNumber + 1n); // 9007199254740992n
// globalThis (ES2020) - universal global object
console.log(globalThis); // window in browser, global in Node
// Promise.allSettled (ES2020)
const promises = [
Promise.resolve(1),
Promise.reject("error"),
Promise.resolve(3)
];
const results = await Promise.allSettled(promises);
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "error" },
// { status: "fulfilled", value: 3 }
// ]
// String replaceAll (ES2021)
const text = "foo bar foo baz foo";
console.log(text.replaceAll("foo", "qux"));
// "qux bar qux baz qux"
// Numeric separators (ES2021)
const billion = 1_000_000_000;
const bytes = 0xFF_FF_FF_FF;
const bits = 0b1010_0001_1000_0101;
// Array at() (ES2022) - negative indexing
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1)); // 5 (last element)
console.log(arr.at(-2)); // 4 (second to last)
// Object.hasOwn (ES2022)
const obj = { prop: "value" };
console.log(Object.hasOwn(obj, "prop")); // true
// Better than obj.hasOwnProperty("prop")
// Error cause (ES2022)
try {
throw new Error("High level error", {
cause: new Error("Root cause")
});
} catch (e) {
console.log(e.cause); // Error: Root cause
}
// Top-level await (ES2022) - in modules
const data = await fetch("/api/data").then(r => r.json());
// Array findLast/findLastIndex (ES2023)
const nums = [1, 2, 3, 4, 3, 2, 1];
console.log(nums.findLast(n => n > 2)); // 3 (last match)
console.log(nums.findLastIndex(n => n > 2)); // 4 (index of last match)
// Array toSorted, toReversed, toSpliced, with (ES2023)
// Non-mutating versions of array methods
const original = [3, 1, 2];
const sorted = original.toSorted(); // [1, 2, 3]
console.log(original); // [3, 1, 2] (unchanged!)
const reversed = original.toReversed(); // [2, 1, 3]
const withChange = original.with(0, 9); // [9, 1, 2]
const spliced = original.toSpliced(1, 1, 5); // [3, 5, 2]
Modern features require modern browsers. Use tools like Babel to transpile for older browser support, or check compatibility on caniuse.com.
Private Class Fields & Methods
ES2022 introduced true private fields and methods using the # prefix. Unlike the underscore convention, these are enforced by the language.
Private Fields
class BankAccount {
// Private fields (must be declared at class level)
#balance = 0;
#transactions = [];
#pin;
// Static private field
static #totalAccounts = 0;
constructor(initialBalance, pin) {
this.#balance = initialBalance;
this.#pin = pin;
BankAccount.#totalAccounts++;
}
// Private method
#validatePin(pin) {
return this.#pin === pin;
}
// Private getter
get #formattedBalance() {
return `$${this.#balance.toFixed(2)}`;
}
// Public methods accessing private fields
deposit(amount, pin) {
if (!this.#validatePin(pin)) {
throw new Error('Invalid PIN');
}
this.#balance += amount;
this.#transactions.push({ type: 'deposit', amount, date: new Date() });
return this.#formattedBalance;
}
withdraw(amount, pin) {
if (!this.#validatePin(pin)) throw new Error('Invalid PIN');
if (amount > this.#balance) throw new Error('Insufficient funds');
this.#balance -= amount;
this.#transactions.push({ type: 'withdraw', amount, date: new Date() });
return this.#formattedBalance;
}
getBalance(pin) {
if (!this.#validatePin(pin)) throw new Error('Invalid PIN');
return this.#formattedBalance;
}
// Static private method
static #generateAccountNumber() {
return Math.random().toString(36).substr(2, 9).toUpperCase();
}
static getAccountCount() {
return BankAccount.#totalAccounts;
}
}
const account = new BankAccount(1000, '1234');
account.deposit(500, '1234'); // "$1500.00"
// These fail:
// account.#balance // SyntaxError: Private field
// account['#balance'] // undefined (not the same thing)
// account.#validatePin() // SyntaxError
Private Field Checks
// ES2022: Check if object has private field (ergonomic brand check)
class MyClass {
#privateField = 42;
static isMyClass(obj) {
// Can check if obj has the private field
return #privateField in obj;
}
// Useful for ensuring methods are called on correct instances
doSomething() {
if (!(#privateField in this)) {
throw new TypeError('Invalid receiver');
}
return this.#privateField;
}
}
const instance = new MyClass();
const fake = { doSomething: instance.doSomething };
MyClass.isMyClass(instance); // true
MyClass.isMyClass(fake); // false
MyClass.isMyClass({}); // false
ES2020 Features
ECMAScript 2020 brought optional chaining, nullish coalescing, BigInt, and more significant improvements.
// Optional Chaining (?.)
const user = {
profile: {
address: { city: 'NYC' }
}
};
const city = user?.profile?.address?.city; // 'NYC'
const zip = user?.profile?.address?.zip; // undefined (no error)
// With methods
const result = obj?.method?.(); // Call if method exists
// With arrays
const first = arr?.[0]; // Safe array access
// With dynamic properties
const prop = obj?.[dynamicKey];
// Nullish Coalescing (??)
// Only falls back for null/undefined (not falsy values!)
const value = null ?? 'default'; // 'default'
const zero = 0 ?? 'default'; // 0 (0 is not nullish)
const empty = '' ?? 'default'; // '' (empty string is not nullish)
const falsy = false ?? 'default'; // false
// Combine with optional chaining
const name = user?.profile?.name ?? 'Anonymous';
// Logical assignment operators (ES2021)
let a = null;
a ??= 'default'; // a = 'default' (only if a is nullish)
let b = '';
b ||= 'default'; // b = 'default' (if b is falsy)
let c = 0;
c &&= 10; // c = 0 (only assigns if c is truthy)
// BigInt - arbitrary precision integers
const big = 9007199254740991n; // 'n' suffix makes it BigInt
const alsoBig = BigInt('9007199254740991');
console.log(big + 1n); // 9007199254740992n (correct!)
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992 (unsafe!)
// Can't mix BigInt with regular numbers
// big + 1 // TypeError!
// Must convert explicitly
const mixed = big + BigInt(1);
// Promise.allSettled - never rejects
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Request ${i} succeeded:`, result.value);
} else {
console.log(`Request ${i} failed:`, result.reason);
}
});
// globalThis - consistent global object
globalThis.myGlobal = 42; // Works in browser, Node, workers, etc.
ES2021-2024 Features
// String.prototype.replaceAll (ES2021)
const str = 'foo bar foo';
str.replaceAll('foo', 'baz'); // 'baz bar baz'
// Numeric separators (ES2021)
const billion = 1_000_000_000;
const bytes = 0xFF_FF_FF_FF;
const binary = 0b1010_0001_1000_0101;
// WeakRef and FinalizationRegistry (ES2021)
// For advanced memory management
const weakRef = new WeakRef(largeObject);
weakRef.deref(); // Returns object or undefined if collected
// Promise.any (ES2021) - first to succeed
try {
const first = await Promise.any([
fetch('/fast-server'),
fetch('/slow-server'),
fetch('/backup-server')
]);
console.log('First successful:', first);
} catch (e) {
console.log('All failed:', e.errors);
}
// Object.hasOwn (ES2022) - safer than hasOwnProperty
const obj = { name: 'Alice' };
Object.hasOwn(obj, 'name'); // true
Object.hasOwn(obj, 'toString'); // false (inherited)
// Works with objects that have null prototype
const nullProto = Object.create(null);
nullProto.key = 'value';
Object.hasOwn(nullProto, 'key'); // true
// nullProto.hasOwnProperty('key') // Error! No such method
// Array.prototype.at (ES2022) - negative indexing
const arr = ['a', 'b', 'c', 'd'];
arr.at(-1); // 'd' (last element)
arr.at(-2); // 'c' (second to last)
arr.at(0); // 'a'
// Also works on strings
'hello'.at(-1); // 'o'
// RegExp match indices (ES2022)
const regex = /(?\w+)/d; // 'd' flag for indices
const match = regex.exec('hello world');
match.indices[0]; // [0, 5] - start and end of match
match.indices.groups.word; // [0, 5] - named group indices
// Error cause (ES2022)
try {
await fetch('/api/data');
} catch (err) {
throw new Error('Failed to load data', { cause: err });
}
// Array methods: toSorted, toReversed, toSpliced, with (ES2023)
const numbers = [3, 1, 2];
numbers.toSorted(); // [1, 2, 3] - original unchanged
numbers.toReversed(); // [2, 1, 3] - original unchanged
numbers.toSpliced(1, 1, 99); // [3, 99, 2] - original unchanged
numbers.with(0, 10); // [10, 1, 2] - original unchanged
// findLast / findLastIndex (ES2023)
const data = [1, 2, 3, 4, 3, 2];
data.findLast(x => x > 2); // 3 (last match)
data.findLastIndex(x => x > 2); // 4 (index of last match)
// Hashbang grammar (ES2023)
// #!/usr/bin/env node
// console.log('Script executed directly');
// Symbols as WeakMap keys (ES2023)
const symbolKey = Symbol('metadata');
const weakMap = new WeakMap();
weakMap.set(symbolKey, { data: 'value' });
// Promise.withResolvers (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();
// Later...
resolve('done!');
// Object.groupBy (ES2024)
const items = [
{ type: 'fruit', name: 'apple' },
{ type: 'vegetable', name: 'carrot' },
{ type: 'fruit', name: 'banana' }
];
const grouped = Object.groupBy(items, item => item.type);
// { fruit: [...], vegetable: [...] }
// Map.groupBy (ES2024)
const mapGrouped = Map.groupBy(items, item => item.type);
// Map { 'fruit' => [...], 'vegetable' => [...] }
- TC39 proposals: Follow github.com/tc39/proposals for upcoming features
- Stage 4: Features at stage 4 will be in the next ECMAScript version
- Browser support: Check caniuse.com or MDN for compatibility
- Transpilers: Use Babel or TypeScript for older browser support
Summary
Destructuring
Extract values from objects and arrays
Spread/Rest
... for expanding and collecting values
Optional Chaining
?. for safe property access
Nullish Coalescing
?? for null/undefined defaults
Map/Set
Specialized collections
Generators
function* for custom iterators