En esta página

Patrones modernos de JavaScript

10 min lectura TextoCap. 5 — JavaScript moderno

Destructuring avanzado

Ya vimos destructuring básico. Ahora exploramos patrones avanzados:

Destructuring anidado con defaults

const { a: { b = 'default' } = {} } = respuesta;

El = {} previene errores si a es undefined.

Destructuring en parametros

Es un patrón muy comun para funciones con muchos parametros:

function configurar({ host = 'localhost', puerto = 3000, ssl = false } = {}) {
  console.log(`${ssl ? 'https' : 'http'}://${host}:${puerto}`);
}
configurar({ ssl: true }); // usa defaults para host y puerto
configurar();              // usa todos los defaults

Optional chaining (?.)

Accede a propiedades profundas sin verificaciones manuales. Retorna undefined si alguna parte es null o undefined:

// Sin optional chaining
const ciudad = usuario && usuario.dirección && usuario.dirección.ciudad;

// Con optional chaining
const ciudad = usuario?.dirección?.ciudad;

Funciona también con arrays (arr?.[0]) y métodos (obj.método?.()).

Nullish coalescing (??)

Proporciona un valor por defecto solo cuando el lado izquierdo es null o undefined:

const puerto = config.puerto ?? 3000;

A diferencia de ||, no considera 0, '' o false como "vacios".

Operadores de asignación lógica

Combinan un operador lógico con asignación:

Operador Equivale a Asigna cuando...
a ||= b a = a || b a es falsy
a ??= b a = a ?? b a es null/undefined
a &&= b a = a && b a es truthy

structuredClone

La forma nativa de hacer copias profundas (ES2022). Funciona con objetos, arrays, Date, Map, Set, RegExp y más:

const copia = structuredClone(original);

No funciona con funciones, nodos DOM ni Symbols.

Map y Set

Map

Similar a un objeto, pero con ventajas:

  • Las claves pueden ser cualquier tipo (objetos, funciones, etc.)
  • Mantiene el orden de inserción
  • Tiene propiedad .size
  • Mejor rendimiento para adiciones/eliminaciones frecuentes

Set

Coleccion de valores unicos. Excelente para eliminar duplicados y verificar pertenencia:

const ids = new Set();
ids.add(1);
ids.add(2);
ids.add(1); // ignorado (ya existe)
ids.has(1); // true
ids.size;   // 2

Iteradores personalizados

Cualquier objeto puede ser iterable implementando [Symbol.iterator](). Esto permite usarlo con for...of, spread, destructuring y más.

Proxy

Un Proxy envuelve un objeto e intercepta operaciones como lectura, escritura y eliminacion de propiedades. Es útil para validación, logging, y patrones reactivos.


Práctica

  1. Elimina duplicados con Set: Dado un array con valores repetidos, usa new Set() y el spread operator para crear un nuevo array sin duplicados. Verifica el resultado con console.log.
  2. Crea un cache con Map: Implementa una funcion obtenerConCache(clave, funcionCostosa) que almacene resultados en un Map. Si la clave ya existe, retorna el valor cacheado; si no, ejecuta la funcion, guarda el resultado y lo retorna.
  3. Valida con Proxy: Crea un Proxy sobre un objeto vacio que valide que la propiedad email siempre contenga un @ al asignarla. Si no lo contiene, lanza un TypeError.

En la siguiente leccion aplicaras todo lo aprendido en un proyecto integrador.

Map vs objeto plano
Usa Map cuando las claves no son strings, cuando el número de entradas cambia frecuentemente, o cuando necesitas el tamanio con .size. Usa objetos planos para estructuras estaticas con claves string.
structuredClone vs spread
El spread operator ({...obj}) solo hace copia superficial. structuredClone hace copia profunda y soporta Date, Map, Set, ArrayBuffer y más. Es la forma estandar de clonar objetos complejos.
javascript
// === DESTRUCTURING AVANZADO ===

// Destructuring con renombre y default
const respuesta = { data: { user: 'Carlos' }, status: 200 };
const { data: { user: nombre }, status = 0 } = respuesta;
console.log(nombre); // 'Carlos'

// Destructuring en parametros de función
function crearPerfil({ nombre, edad, rol = 'usuario' }) {
  return `${nombre} (${edad}) - ${rol}`;
}
crearPerfil({ nombre: 'Ana', edad: 25 });

// === OPTIONAL CHAINING ===
const config = { db: { host: 'localhost' } };
const puerto = config.db?.puerto ?? 3000;
const nombre2 = config.auth?.user?.nombre ?? 'anonimo';

// Optional chaining con métodos
const resultado = arreglo?.find?.(x => x.id === 1);

// === LOGICAL ASSIGNMENT ===
let opciones = { tema: '', idioma: null };

opciones.tema ||= 'claro';    // asigna si es falsy
opciones.idioma ??= 'es';     // asigna si es null/undefined
opciones.debug &&= false;     // asigna si es truthy

// === STRUCTUREDCLONE ===
const original = {
  usuario: { nombre: 'Carlos', tags: ['dev'] },
  fecha: new Date(),
};
const copia = structuredClone(original);
copia.usuario.tags.push('senior');
console.log(original.usuario.tags); // ['dev'] (no afectado)
javascript
// === MAP Y SET ===
const cache = new Map();
cache.set('user:1', { nombre: 'Carlos' });
cache.set('user:2', { nombre: 'Ana' });
console.log(cache.get('user:1'));  // {nombre: 'Carlos'}
console.log(cache.has('user:3')); // false

const unicos = new Set([1, 2, 2, 3, 3, 3]);
console.log([...unicos]); // [1, 2, 3]

// Eliminar duplicados de un array
const sinDuplicados = [...new Set(nombres)];

// === ITERADORES Y FOR...OF ===
// Objeto iterable personalizado
const rango = {
  desde: 1,
  hasta: 5,
  [Symbol.iterator]() {
    let actual = this.desde;
    const hasta = this.hasta;
    return {
      next() {
        return actual <= hasta
          ? { value: actual++, done: false }
          : { done: true };
      },
    };
  },
};

for (const n of rango) {
  console.log(n); // 1, 2, 3, 4, 5
}

// === PROXY ===
const validado = new Proxy({}, {
  set(obj, prop, valor) {
    if (prop === 'edad' && typeof valor !== 'number') {
      throw new TypeError('edad debe ser un número');
    }
    obj[prop] = valor;
    return true;
  },
});

validado.nombre = 'Carlos'; // OK
validado.edad = 28;         // OK
// validado.edad = 'veintiocho'; // TypeError!