On this page

Modern JavaScript patterns

10 min read TextCh. 5 — Modern JavaScript

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 defaults

Optional 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 .size property
  • 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;   // 2

Custom 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

  1. 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 with console.log.
  2. Create a cache with Map: Implement a function getWithCache(key, expensiveFunction) that stores results in a Map. If the key already exists, return the cached value; otherwise, execute the function, store the result, and return it.
  3. Validate with Proxy: Create a Proxy over an empty object that validates the email property always contains an @ when assigned. If it does not, throw a TypeError.

In the next lesson you will apply everything you have learned in an integrative project.

Map vs plain object
Use Map when keys are not strings, when the number of entries changes frequently, or when you need the size with .size. Use plain objects for static structures with string keys.
structuredClone vs spread
The spread operator ({...obj}) only makes a shallow copy. structuredClone makes a deep copy and supports Date, Map, Set, ArrayBuffer, and more. It is the standard way to clone complex objects.
javascript
// === 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)
javascript
// === 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!