Si llevas un tiempo con JavaScript y quieres escribir código más limpio, expresivo y moderno, la desestructuración y el operador spread (y su primo, el rest) son dos de las herramientas más útiles que puedes aprender. Aparecen constantemente en proyectos React, Angular, Node.js y en cualquier código JavaScript moderno. Esta guía te explica ambas desde cero, con ejemplos reales y casos de uso que verás en el trabajo diario.

En este artículo:

¿Qué es la desestructuración en JavaScript?

La desestructuración (destructuring en inglés) es una sintaxis introducida en ES6 que te permite extraer valores de arrays u objetos y asignarlos a variables de forma mucho más concisa. En lugar de acceder a cada propiedad una a una, puedes hacerlo todo en una sola línea.

Antes de ES6, para extraer propiedades de un objeto, tenías que escribir:

const usuario = { nombre: 'Ana', edad: 28, ciudad: 'Madrid' };

// Forma antigua
const nombre = usuario.nombre;
const edad = usuario.edad;
const ciudad = usuario.ciudad;

Con la desestructuración, puedes hacer exactamente lo mismo en una sola línea:

const { nombre, edad, ciudad } = usuario;
console.log(nombre); // 'Ana'
console.log(edad);   // 28

Es una mejora puramente de legibilidad, pero el impacto en el código real es enorme. Una vez que te acostumbras, leer código sin desestructuración se siente tedioso.

Desestructuración de arrays

Con los arrays, la desestructuración funciona por posición. Las variables que declaras reciben el valor del elemento en esa posición del array:

const colores = ['rojo', 'verde', 'azul'];

const [primero, segundo, tercero] = colores;
console.log(primero);  // 'rojo'
console.log(segundo);  // 'verde'
console.log(tercero);  // 'azul'

Puedes saltarte elementos usando comas sin nombre de variable:

const [, , tercero] = colores;
console.log(tercero); // 'azul'

También puedes capturar el resto del array usando el operador rest (lo veremos más adelante con más detalle):

const numeros = [1, 2, 3, 4, 5];
const [primero, segundo, ...resto] = numeros;
console.log(primero); // 1
console.log(segundo); // 2
console.log(resto);   // [3, 4, 5]

Un uso muy frecuente de la desestructuración de arrays es el intercambio de variables sin necesidad de una variable temporal:

let a = 1;
let b = 2;

[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1

Este patrón es especialmente útil en algoritmos de ordenación y manipulación de datos.

Desestructuración de objetos

La desestructuración de objetos es probablemente la que más usarás en el día a día, especialmente cuando trabajas con APIs, props de React o configuraciones. La diferencia con los arrays es que aquí usas llaves {} y el nombre de la variable debe coincidir con el nombre de la propiedad del objeto:

const producto = {
  id: 101,
  nombre: 'Teclado mecánico',
  precio: 89.99,
  categoria: 'periféricos'
};

const { nombre, precio } = producto;
console.log(nombre); // 'Teclado mecánico'
console.log(precio); // 89.99

Si quieres usar un nombre diferente para la variable, puedes renombrarla con dos puntos:

const { nombre: nombreProducto, precio: coste } = producto;
console.log(nombreProducto); // 'Teclado mecánico'
console.log(coste);          // 89.99

La desestructuración anidada también es posible cuando tienes objetos dentro de objetos:

const usuario = {
  nombre: 'Carlos',
  direccion: {
    calle: 'Gran Vía',
    ciudad: 'Madrid',
    cp: '28013'
  }
};

const { nombre, direccion: { ciudad, cp } } = usuario;
console.log(nombre);  // 'Carlos'
console.log(ciudad);  // 'Madrid'
console.log(cp);      // '28013'

Ojo: en la desestructuración anidada, la variable direccion no se crea (solo se usa como patrón para extraer ciudad y cp). Si también quieres tener direccion como variable, debes declararla aparte.

Valores por defecto

Una característica muy útil de la desestructuración es la posibilidad de asignar valores por defecto cuando una propiedad o elemento no existe (es undefined). Esto evita comprobaciones manuales con operadores ternarios o ||:

const config = { tema: 'oscuro' };

const { tema, idioma = 'es', tamanioFuente = 16 } = config;
console.log(tema);          // 'oscuro'
console.log(idioma);        // 'es'    (valor por defecto)
console.log(tamanioFuente); // 16      (valor por defecto)

Los valores por defecto solo se aplican cuando la propiedad es undefined. Si la propiedad existe pero su valor es null, el valor por defecto no se aplica:

const { x = 10, y = 20 } = { x: null, y: 5 };
console.log(x); // null  (no se aplica el valor por defecto, x existe)
console.log(y); // 5

También puedes combinar renombrado y valores por defecto en la misma desestructuración:

const { nombre: nombreUsuario = 'Anónimo' } = {};
console.log(nombreUsuario); // 'Anónimo'

Desestructuración en parámetros de funciones

Uno de los usos más potentes y frecuentes de la desestructuración es directamente en los parámetros de una función. En lugar de recibir un objeto completo y acceder a sus propiedades dentro de la función, puedes desestructurarlo directamente en la firma:

// Sin desestructuración
function mostrarUsuario(usuario) {
  console.log(usuario.nombre + ' - ' + usuario.edad);
}

// Con desestructuración
function mostrarUsuario({ nombre, edad }) {
  console.log(nombre + ' - ' + edad);
}

mostrarUsuario({ nombre: 'Laura', edad: 32, ciudad: 'Barcelona' });
// Laura - 32

Esto hace el código más autoexplicativo: sabes exactamente qué propiedades espera la función sin necesidad de leer su implementación.

En React, esto es omnipresente en los componentes funcionales:

// Sin desestructuración
function Tarjeta(props) {
  return <h1>{props.titulo}</h1>;
}

// Con desestructuración (forma estándar en React)
function Tarjeta({ titulo, descripcion, imagen }) {
  return (
    <div>
      <img src={imagen} alt={titulo} />
      <h1>{titulo}</h1>
      <p>{descripcion}</p>
    </div>
  );
}

También puedes combinar desestructuración con valores por defecto en los parámetros:

function crearBoton({ texto = 'Aceptar', color = 'azul', deshabilitado = false } = {}) {
  console.log(texto, color, deshabilitado);
}

crearBoton({ texto: 'Cancelar' }); // Cancelar azul false
crearBoton();                      // Aceptar azul false (gracias al = {} final)

El = {} al final del parámetro es un patrón importante: garantiza que la función funcione incluso si se llama sin argumentos, evitando el error «Cannot destructure property of undefined».

El operador spread (…)

El operador spread (...) hace lo contrario a la desestructuración: en lugar de extraer valores, los expande o dispersa. Permite expandir los elementos de un array u objeto en otro contexto, como otro array, una llamada a función o un nuevo objeto.

Spread con arrays:

const frutas = ['manzana', 'pera', 'naranja'];
const verduras = ['zanahoria', 'brócoli'];

// Combinar arrays sin mutar los originales
const alimentos = [...frutas, ...verduras];
console.log(alimentos);
// ['manzana', 'pera', 'naranja', 'zanahoria', 'brócoli']

// Clonar un array (copia superficial)
const copiaFrutas = [...frutas];
copiaFrutas.push('mango');
console.log(frutas);     // ['manzana', 'pera', 'naranja'] (no afectado)
console.log(copiaFrutas); // ['manzana', 'pera', 'naranja', 'mango']

Spread con objetos:

const datosBase = { nombre: 'Pedro', edad: 25 };
const datosExtra = { ciudad: 'Sevilla', profesion: 'desarrollador' };

// Combinar objetos
const usuarioCompleto = { ...datosBase, ...datosExtra };
console.log(usuarioCompleto);
// { nombre: 'Pedro', edad: 25, ciudad: 'Sevilla', profesion: 'desarrollador' }

// Sobrescribir propiedades
const usuarioActualizado = { ...datosBase, edad: 26, email: 'pedro@ejemplo.com' };
console.log(usuarioActualizado);
// { nombre: 'Pedro', edad: 26, email: 'pedro@ejemplo.com' }

Este patrón de { ...objeto, propiedad: nuevoValor } es fundamental en React y Redux para actualizar el estado de forma inmutable, sin modificar el objeto original.

Spread en llamadas a funciones:

const numeros = [3, 1, 4, 1, 5, 9, 2, 6];

// Sin spread (no funciona así con Math.max)
// console.log(Math.max(numeros)); // NaN

// Con spread (expande el array como argumentos individuales)
console.log(Math.max(...numeros)); // 9

// También útil con funciones propias
function sumar(a, b, c) {
  return a + b + c;
}
const valores = [2, 4, 6];
console.log(sumar(...valores)); // 12

El operador rest (…)

El operador rest usa la misma sintaxis que el spread (...), pero su función es opuesta: en lugar de expandir, recoge o agrupa los elementos restantes en un array o objeto. Se usa en contextos de desestructuración y en parámetros de funciones.

Rest en desestructuración de arrays:

const [primero, segundo, ...restantes] = [10, 20, 30, 40, 50];
console.log(primero);    // 10
console.log(segundo);    // 20
console.log(restantes);  // [30, 40, 50]

Rest en desestructuración de objetos:

const { nombre, edad, ...otrosDatos } = {
  nombre: 'Marta',
  edad: 29,
  ciudad: 'Valencia',
  profesion: 'diseñadora',
  hobbies: ['pintura', 'senderismo']
};

console.log(nombre);     // 'Marta'
console.log(edad);       // 29
console.log(otrosDatos); // { ciudad: 'Valencia', profesion: 'diseñadora', hobbies: [...] }

Rest en parámetros de funciones:

function registrarEventos(tipo, ...detalles) {
  console.log('Tipo:', tipo);
  console.log('Detalles:', detalles);
}

registrarEventos('click', 'botón-enviar', 'formulario-contacto', '14:23:05');
// Tipo: click
// Detalles: ['botón-enviar', 'formulario-contacto', '14:23:05']

El parámetro rest siempre debe ser el último en la lista de parámetros. No puedes tener function f(...a, b); JavaScript lanzará un error de sintaxis.

Casos prácticos reales

Estos conceptos cobran aún más sentido cuando los ves en contextos de código real. Aquí tienes algunos patrones que encontrarás constantemente en proyectos profesionales:

Procesar respuesta de una API:

// Respuesta típica de una API de usuarios
const respuestaAPI = {
  data: {
    id: 42,
    username: 'devmaster',
    email: 'dev@ejemplo.com',
    role: 'admin',
    createdAt: '2026-01-15'
  },
  status: 200,
  message: 'OK'
};

// Extraer solo lo que necesitas
const { data: { username, email, role }, status } = respuestaAPI;
console.log(username); // 'devmaster'
console.log(role);     // 'admin'
console.log(status);   // 200

Actualización inmutable de estado en React:

// Estado actual
const state = {
  usuario: { nombre: 'Ana', puntos: 100 },
  configuracion: { tema: 'oscuro', notificaciones: true }
};

// Actualizar puntos sin mutar el estado original
const nuevoState = {
  ...state,
  usuario: { ...state.usuario, puntos: state.usuario.puntos + 50 }
};

console.log(state.usuario.puntos);      // 100 (no mutado)
console.log(nuevoState.usuario.puntos); // 150

Filtrar propiedades de un objeto (omitir campos):

// Útil para no enviar datos sensibles a la API
const usuarioCompleto = {
  id: 1,
  nombre: 'Roberto',
  email: 'roberto@ejemplo.com',
  password: 'hash_secreto_123',
  token: 'jwt_interno_xyz'
};

// Extraer todo excepto campos sensibles
const { password, token, ...usuarioPublico } = usuarioCompleto;
console.log(usuarioPublico);
// { id: 1, nombre: 'Roberto', email: 'roberto@ejemplo.com' }

Combinar múltiples objetos de configuración:

const configDefecto = {
  timeout: 5000,
  reintentos: 3,
  cache: true,
  formato: 'json'
};

const configUsuario = {
  timeout: 10000,
  cache: false
};

// La configuración del usuario sobrescribe la de defecto
const configuracionFinal = { ...configDefecto, ...configUsuario };
console.log(configuracionFinal);
// { timeout: 10000, reintentos: 3, cache: false, formato: 'json' }

Errores comunes

Antes de terminar, repasemos los errores más frecuentes que cometen los desarrolladores cuando empiezan a usar estas características:

1. Desestructurar null o undefined sin protección:

// Error frecuente en respuestas de API que pueden ser null
const datos = null;
const { nombre } = datos; // TypeError: Cannot destructure property 'nombre' of null

// Solución: usar valor por defecto o optional chaining
const { nombre } = datos || {};  // nombre será undefined, no lanzará error
const nombre2 = datos?.nombre;   // undefined, usando optional chaining

2. Confundir spread superficial con copia profunda:

const original = {
  nombre: 'Sara',
  preferencias: { idioma: 'es', color: 'azul' }
};

const copia = { ...original };

// Modificar el objeto anidado afecta a ambos (es una referencia compartida)
copia.preferencias.idioma = 'en';
console.log(original.preferencias.idioma); // 'en' (!!)

// Para copia profunda, usa structuredClone() o JSON.parse(JSON.stringify(...))
const copiaReal = structuredClone(original);

3. El operador rest no puede ir en posición que no sea la última:

// Error de sintaxis:
const { ...resto, nombre } = objeto; // SyntaxError!

// Correcto:
const { nombre, ...resto } = objeto;

4. Spread de objetos no clona métodos de clase ni prototype:

class Persona {
  constructor(nombre) { this.nombre = nombre; }
  saludar() { return 'Hola, soy ' + this.nombre; }
}

const persona = new Persona('Elena');
const copia = { ...persona };

console.log(copia.nombre);    // 'Elena' (propiedades copiadas)
console.log(copia.saludar()); // TypeError: copia.saludar is not a function
// Los métodos del prototipo no se copian con spread

Conclusión

La desestructuración y el operador spread/rest son dos de las características de ES6 que más impacto tienen en la calidad y legibilidad del código JavaScript moderno. Una vez que los dominas, los usarás en absolutamente todos tus proyectos: al consumir APIs, al manejar estado en React, al escribir funciones utilitarias y al estructurar configuraciones.

Si quieres seguir profundizando en JavaScript moderno, te recomiendo leer sobre Promesas y async/await en JavaScript para manejar código asíncrono, y si ya dominas estos fundamentos, el artículo sobre Fetch API para consumir APIs REST te mostrará cómo aplicarlos en escenarios reales de trabajo con servidores.