Desestructuración y spread en JS
En esta lección
Los tres puntos (...) y las llaves y corchetes a la izquierda de un = aparecen en casi todo el código moderno: en React, en las respuestas de una API, en las funciones con muchas opciones. Son la desestructuración, el spread y el rest. No hacen nada que no pudieras hacer antes con más líneas, pero hacen el código mucho más corto y claro. En esta lección aprenderás a sacar datos de arrays y objetos, a copiarlos y fusionarlos sin modificar el original, y a entender cuándo una copia es de verdad una copia.
Necesitas tener claros los arrays y los objetos.
Desestructurar arrays
Desestructurar es sacar valores de un array o un objeto y guardarlos en variables en una sola línea. En un array, los valores se asignan por posición:
const podio = ["Ana", "Luis", "Eva", "Marta"];
// Sin desestructuración
const primero = podio[0];
const segundo = podio[1];
// Con desestructuración
const [oro, plata, bronce] = podio;
console.log(oro, plata, bronce); // Ana Luis Eva
Los corchetes a la izquierda del = no crean un array: indican “saca de aquí las posiciones 0, 1 y 2”. Si pides más variables que elementos hay, las que sobran valen undefined.
Saltar elementos
Deja un hueco entre comas:
const [campeona, , tercera] = podio;
console.log(campeona, tercera); // Ana Eva
Valores por defecto
Se usan si la posición no existe (o vale undefined):
const [x = 1, y = 2] = [10];
console.log(x, y); // 10 2
Intercambiar variables
El truco clásico, sin variable auxiliar:
let izquierda = "A";
let derecha = "B";
[izquierda, derecha] = [derecha, izquierda];
console.log(izquierda, derecha); // B A
Un caso real
Separar una fecha en año, mes y día:
const [anio, mes, dia] = "2026-09-30".split("-").map(Number);
console.log(anio, mes, dia); // 2026 9 30
Desestructurar objetos
Con objetos se usan llaves y los valores se asignan por nombre, no por posición. El orden da igual:
const libro = {
titulo: "Rayuela",
autor: "Cortázar",
paginas: 600,
editorial: { nombre: "Alfaguara", ciudad: "Madrid" },
};
const { autor, titulo } = libro;
console.log(titulo, autor); // Rayuela Cortázar
La variable se llama igual que la propiedad. Si la propiedad no existe, la variable vale undefined.
Renombrar
Si ya tienes una variable con ese nombre o prefieres otro, usa propiedad: nuevoNombre:
const { titulo: nombreLibro } = libro;
console.log(nombreLibro); // Rayuela
Se lee “coge titulo y guárdalo en nombreLibro”. Es fácil confundirlo con un objeto: en la desestructuración, lo de la derecha de los dos puntos es la variable.
Valores por defecto
const { isbn = "sin ISBN", paginas = 0 } = libro;
console.log(isbn, paginas); // sin ISBN 600
paginas existe, así que se ignora el 0. Se puede combinar con renombrar: const { isbn: codigo = "sin ISBN" } = libro;.
Desestructuración anidada
Para sacar algo de un objeto que está dentro de otro, repite la forma:
const { editorial: { ciudad } } = libro;
console.log(ciudad); // Madrid
Ojo: aquí se crea la variable ciudad, pero no editorial. Y si editorial no existiera, daría error. No anides más de dos niveles: se vuelve ilegible.
Desestructurar en los parámetros
Es donde más se usa. Cuando una función recibe un objeto, puedes desestructurarlo en la propia firma:
function ficha({ titulo, autor, anio = "desconocido" }) {
return `${titulo} (${autor}, ${anio})`;
}
ficha(libro); // "Rayuela (Cortázar, desconocido)"
Para funciones con muchas opciones es ideal, porque quien la llama pasa solo las que quiere, en cualquier orden y con nombre:
function crearBoton({ texto = "Aceptar", color = "azul", tamanio = "normal" } = {}) {
return `${texto}-${color}-${tamanio}`;
}
crearBoton({ color: "rojo" }); // "Aceptar-rojo-normal"
crearBoton(); // "Aceptar-azul-normal"
El = {} final es importante: si llamas a la función sin argumentos, desestructurar undefined falla con TypeError: Cannot destructure property 'texto' of 'undefined'.
También funciona en bucles y callbacks:
const usuarios = [
{ nombre: "Ana", edad: 30 },
{ nombre: "Luis", edad: 17 },
];
for (const { nombre, edad } of usuarios) {
console.log(nombre, edad);
}
// Ana 30
// Luis 17
usuarios.map(({ nombre }) => nombre); // [ 'Ana', 'Luis' ]
for (const [clave, valor] of Object.entries({ a: 1, b: 2 })) {
console.log(clave, valor); // a 1, b 2
}
Spread: expandir con ...
El operador spread “despliega” los elementos de un array (o las propiedades de un objeto) en otro sitio.
Spread en arrays
const frutas = ["pera", "kiwi"];
const verduras = ["col"];
const compra = [...frutas, "uva", ...verduras];
// [ 'pera', 'kiwi', 'uva', 'col' ]
const copia = [...frutas];
copia.push("mango");
console.log(frutas, copia); // [ 'pera', 'kiwi' ] [ 'pera', 'kiwi', 'mango' ]
También sirve para pasar un array como argumentos sueltos y para convertir en array cualquier cosa recorrible:
Math.max(...[4, 9, 2]); // 9 (igual que Math.max(4, 9, 2))
[..."hola"]; // [ 'h', 'o', 'l', 'a' ]
[...new Set([1, 1, 2])]; // [ 1, 2 ]
Spread en objetos
Copia las propiedades de un objeto dentro de otro. Si una propiedad se repite, gana la última:
const porDefecto = { tema: "claro", idioma: "es", avisos: true };
const delUsuario = { tema: "oscuro" };
const config = { ...porDefecto, ...delUsuario };
// { tema: 'oscuro', idioma: 'es', avisos: true }
Por eso el orden importa: primero los valores por defecto, después los que deben ganar. Es la forma habitual de “cambiar” una propiedad sin tocar el original:
const reedicion = { ...libro, paginas: 650 };
console.log(reedicion.paginas, libro.paginas); // 650 600
Copia superficial frente a copia profunda
Aquí está la trampa. El spread hace una copia superficial: copia el primer nivel, pero los objetos y arrays de dentro se comparten.
const alumno = { nombre: "Eva", notas: [7, 8] };
const copia = { ...alumno };
copia.nombre = "Otra"; // no afecta al original
copia.notas.push(10); // ¡sí afecta! es el mismo array
console.log(alumno); // { nombre: 'Eva', notas: [ 7, 8, 10 ] }
copia.notas y alumno.notas apuntan al mismo array. Entenderás el porqué en el apartado de referencias de objetos.
Para una copia profunda (todos los niveles), usa structuredClone, disponible en todos los navegadores modernos y en Node:
const independiente = structuredClone(alumno);
independiente.notas.push(1);
console.log(alumno.notas, independiente.notas); // [ 7, 8, 10 ] [ 7, 8, 10, 1 ]
structuredClone copia objetos, arrays, fechas, Map y Set, pero no funciones: si el objeto tiene métodos, lanza DataCloneError. Verás en código antiguo JSON.parse(JSON.stringify(obj)); funciona peor (pierde fechas, undefined e Infinity), así que prefiere structuredClone.
| Forma | Nivel | Comparte lo anidado |
|---|---|---|
const b = a | Ninguno: es el mismo objeto | Todo |
[...a] / { ...a } | Superficial | Sí |
structuredClone(a) | Profunda | No |
Rest: recoger lo que sobra
Los mismos tres puntos, pero en el lado contrario, hacen lo opuesto: recogen varios valores en uno. Se llama rest y siempre va el último.
En desestructuración de arrays, recoge los elementos restantes en un array:
const [ganador, ...resto] = podio;
console.log(ganador, resto); // Ana [ 'Luis', 'Eva', 'Marta' ]
En objetos, recoge las propiedades que no has nombrado. Es la forma limpia de quitar una propiedad sin delete:
const { password, ...publico } = { nombre: "Ana", password: "1234" };
console.log(publico); // { nombre: 'Ana' }
En parámetros, recoge los argumentos en un array (lo viste en funciones avanzadas):
function sumar(...numeros) {
return numeros.reduce((total, n) => total + n, 0);
}
sumar(1, 2, 3); // 6
const notas = [5, 8, 6];
sumar(...notas); // 19: spread al llamar, rest al recibir
¿Cómo distinguirlos? Spread está donde se usan valores (a la derecha del =, dentro de [] o {} que se crean, al llamar a una función). Rest está donde se reciben (a la izquierda del =, en los parámetros).
?. y ?? junto a la desestructuración
El encadenamiento opcional ?. y el operador ?? ya los tienes en operadores. Combinan muy bien con lo que has visto cuando los datos pueden venir incompletos, por ejemplo de una API:
const respuesta = { usuario: { direccion: null } };
const ciudad = respuesta.usuario?.direccion?.ciudad ?? "Sin ciudad";
console.log(ciudad); // Sin ciudad
Recuerda que el valor por defecto de la desestructuración solo actúa con undefined, no con null. Si un dato puede llegar como null, usa ??.
Errores frecuentes
- Desestructurar
undefinedonull:const { a } = undefinedlanzaTypeError. En parámetros, añade= {}. - Confundir renombrar con asignar valor:
{ titulo: nombre }crea la variablenombre, no una propiedad. - Creer que spread copia en profundidad: los objetos anidados se comparten; usa
structuredClone. - Poner el rest en medio:
const [...a, b] = listaes unSyntaxError. - Olvidar los paréntesis al desestructurar en una variable ya declarada:
({ nombre } = datos);. Sin ellos, las llaves se toman como un bloque. - Fusionar en el orden equivocado:
{ ...delUsuario, ...porDefecto }pisa las preferencias del usuario.
Resumen
| Sintaxis | Qué hace | Ejemplo |
|---|---|---|
const [a, b] = arr | Saca por posición | const [oro, plata] = podio |
const [a, , c] = arr | Salta elementos | const [x, , z] = [1, 2, 3] |
[a, b] = [b, a] | Intercambia | |
const { a, b } = obj | Saca por nombre | const { titulo } = libro |
const { a: x } = obj | Renombra | const { titulo: nombre } = libro |
const { a = 1 } = obj | Valor por defecto | const { isbn = "-" } = libro |
function f({ a, b } = {}) | Desestructura parámetros | Opciones con nombre |
[...arr], { ...obj } | Spread: copia superficial | { ...base, tema: "oscuro" } |
structuredClone(obj) | Copia profunda | |
const [a, ...resto] = arr | Rest: recoge lo demás | const { password, ...publico } = u |
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.