On this page
Modern JavaScript patterns
Advanced destructuring
We already covered basic destructuring. Now let's explore advanced patterns:
Nested destructuring with defaults
const { a: { b = 'default' } = {} } = response;The = {} prevents errors if a is undefined.
Destructuring in parameters
It is a very common pattern for functions with many parameters:
function configure({ host = 'localhost', port = 3000, ssl = false } = {}) {
console.log(`${ssl ? 'https' : 'http'}://${host}:${port}`);
}
configure({ ssl: true }); // uses defaults for host and port
configure(); // uses all defaultsOptional chaining (?.)
Accesses deep properties without manual checks. Returns undefined if any part is null or undefined:
// Without optional chaining
const city = user && user.address && user.address.city;
// With optional chaining
const city = user?.address?.city;It also works with arrays (arr?.[0]) and methods (obj.method?.()).
Nullish coalescing (??)
Provides a default value only when the left side is null or undefined:
const port = config.port ?? 3000;Unlike ||, it does not consider 0, '', or false as "empty".
Logical assignment operators
They combine a logical operator with assignment:
| Operator | Equivalent to | Assigns when... |
|---|---|---|
a ||= b |
a = a || b |
a is falsy |
a ??= b |
a = a ?? b |
a is null/undefined |
a &&= b |
a = a && b |
a is truthy |
structuredClone
The native way to make deep copies (ES2022). It works with objects, arrays, Date, Map, Set, RegExp, and more:
const copy = structuredClone(original);It does not work with functions, DOM nodes, or Symbols.
Map and Set
Map
Similar to an object, but with advantages:
- Keys can be any type (objects, functions, etc.)
- Maintains insertion order
- Has a
.sizeproperty - Better performance for frequent additions/deletions
Set
A collection of unique values. Excellent for removing duplicates and checking membership:
const ids = new Set();
ids.add(1);
ids.add(2);
ids.add(1); // ignored (already exists)
ids.has(1); // true
ids.size; // 2Custom iterators
Any object can be made iterable by implementing [Symbol.iterator](). This allows it to be used with for...of, spread, destructuring, and more.
Proxy
A Proxy wraps an object and intercepts operations such as reading, writing, and deleting properties. It is useful for validation, logging, and reactive patterns.
Practice
- Remove duplicates with Set: Given an array with repeated values, use
new Set()and the spread operator to create a new array without duplicates. Verify the result withconsole.log. - Create a cache with Map: Implement a function
getWithCache(key, expensiveFunction)that stores results in aMap. If the key already exists, return the cached value; otherwise, execute the function, store the result, and return it. - Validate with Proxy: Create a
Proxyover an empty object that validates theemailproperty always contains an@when assigned. If it does not, throw aTypeError.
In the next lesson you will apply everything you have learned in an integrative project.
// === ADVANCED DESTRUCTURING ===
// Destructuring with rename and default
const response = { data: { user: 'Carlos' }, status: 200 };
const { data: { user: name }, status = 0 } = response;
console.log(name); // 'Carlos'
// Destructuring in function parameters
function createProfile({ name, age, role = 'user' }) {
return `${name} (${age}) - ${role}`;
}
createProfile({ name: 'Ana', age: 25 });
// === OPTIONAL CHAINING ===
const config = { db: { host: 'localhost' } };
const port = config.db?.port ?? 3000;
const userName = config.auth?.user?.name ?? 'anonymous';
// Optional chaining with methods
const result = array?.find?.(x => x.id === 1);
// === LOGICAL ASSIGNMENT ===
let options = { theme: '', language: null };
options.theme ||= 'light'; // assigns if falsy
options.language ??= 'en'; // assigns if null/undefined
options.debug &&= false; // assigns if truthy
// === STRUCTUREDCLONE ===
const original = {
user: { name: 'Carlos', tags: ['dev'] },
date: new Date(),
};
const copy = structuredClone(original);
copy.user.tags.push('senior');
console.log(original.user.tags); // ['dev'] (not affected)
// === MAP AND SET ===
const cache = new Map();
cache.set('user:1', { name: 'Carlos' });
cache.set('user:2', { name: 'Ana' });
console.log(cache.get('user:1')); // {name: 'Carlos'}
console.log(cache.has('user:3')); // false
const unique = new Set([1, 2, 2, 3, 3, 3]);
console.log([...unique]); // [1, 2, 3]
// Remove duplicates from an array
const withoutDuplicates = [...new Set(names)];
// === ITERATORS AND FOR...OF ===
// Custom iterable object
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const end = this.to;
return {
next() {
return current <= end
? { value: current++, done: false }
: { done: true };
},
};
},
};
for (const n of range) {
console.log(n); // 1, 2, 3, 4, 5
}
// === PROXY ===
const validated = new Proxy({}, {
set(obj, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age must be a number');
}
obj[prop] = value;
return true;
},
});
validated.name = 'Carlos'; // OK
validated.age = 28; // OK
// validated.age = 'twenty-eight'; // TypeError!
Sign in to track your progress