En el capítulo de arrays y objetos (Nivel 2) ya dominaste el spread: { ...heroe } para
clonar o enriquecer un objeto, [...lista] para clonar un array, y { ...base, ...cambios }
para fusionar. Si necesitas un repaso, vuelve a ese capítulo.
Aquí hay dos partes: un recordatorio compacto de spread con los usos que aún no viste
(spread en llamadas a función), y el protagonista genuinamente nuevo del capítulo: rest.
El mismo ..., pero recogiendo en vez de repartiendo.
La regla que lo ordena todo: ... a la derecha de un = o en una llamada reparte (spread);
... a la izquierda en un destructuring o en los parámetros recoge (rest).
Spread: recordatorio y uso nuevo#
Los tres usos de spread. Los dos primeros los conoces; el tercero es nuevo:
// ─── Spread en objetos: copia y fusión (ya lo sabes) ──────────────────────
const heroe = { nombre: 'Tracer', rol: 'Daño', partidas: 120, victorias: 78 };
// Copia y pisa un campo: lo escrito después del spread gana.
// el original sigue con victorias: 78
const ascendido = { ...heroe, victorias: heroe.victorias + 1 };
// 79
console.log(ascendido.victorias);
// Fusión: dos objetos, el último gana en las claves repetidas.
const base = { region: 'Europa', modo: 'Competitivo', maxJugadores: 6 };
const override = { modo: 'Amistoso' };
// modo viene de override (el último gana)
const fusion = { ...base, ...override };
// Amistoso
console.log(fusion.modo);// ─── Spread en arrays: combinar e insertar (ya lo sabes) ──────────────────
const tanques = ['Reinhardt', 'Winston'];
const apoyos = ['Mercy', 'Ana'];
// Combina en un array nuevo
const plantilla = [...tanques, ...apoyos];
// Reinhardt, Winston, Mercy, Ana
console.log(plantilla.join(', '));
// Insertar en medio
const conDano = [...tanques, 'Tracer', ...apoyos];
// Reinhardt, Winston, Tracer, Mercy, Ana
console.log(conDano.join(', '));// ─── Spread en una llamada a función: NUEVO ───────────────────────────────
// Cuando una función espera valores sueltos pero tienes un array, ... lo "abre".
const partidas = [120, 90, 200, 150];
// Math.max espera números sueltos: Math.max(120, 90, 200, 150).
// ...partidas lo despliega en argumentos sueltos.
const maximo = Math.max(...partidas);
// equivale a Math.max(120, 90, 200, 150)
// 200
console.log(maximo);
// Sin spread no funciona: Math.max([120, ...]) recibe UN array → NaN.
// NaN
console.log(Math.max(partidas));Ese último uso (spread en llamada) es nuevo. Los otros dos los dominabas del Nivel 2.
Rest: el gemelo que recoge#
Hasta aquí, ... siempre repartía. Su gemelo hace lo contrario: a la izquierda, recoge
lo que sobra. Se llama rest (“el resto”) y es el foco del capítulo.
Rest en los parámetros de una función#
Un parámetro precedido de ... recoge todos los argumentos sobrantes en un array. Debe ir
el último.
// capitan recoge el primer argumento; ...resto agrupa TODOS los demás en un array.
function alinear(capitan, ...resto) {
return capitan + ' lidera a ' + resto.join(', ');
}
console.log(alinear('Mercy', 'Reinhardt', 'Tracer'));
// capitan → 'Mercy' resto → ['Reinhardt', 'Tracer']
// → "Mercy lidera a Reinhardt, Tracer"
// Funciona con cualquier número de argumentos: resto se adapta.
// capitan → 'Ana' resto → []
console.log(alinear('Ana'));Antes de rest existía
arguments, un objeto especial con todos los argumentos. Es código legacy: no es un array de verdad y no funciona en arrow functions. Hoy se usa rest, que sí da un array normal. Lo mencionamos para que lo reconozcas si lo ves, no para que lo uses.
Rest en el destructuring#
Esto enlaza directamente con el capítulo anterior. En un destructuring, ... recoge todo lo
que no nombraste. Con arrays, separa el primero (o los primeros) del resto:
const ordenDePick = ['Mercy', 'Reinhardt', 'Tracer', 'Genji'];
// capitan toma el índice 0; ...linea recoge el resto en un array nuevo.
const [capitan, ...linea] = ordenDePick;
// capitan → 'Mercy' linea → ['Reinhardt', 'Tracer', 'Genji']
console.log('Capitán: ' + capitan);
console.log('Línea: ' + linea.join(', '));Con objetos, saca las claves que nombras y agrupa el resto en un objeto nuevo:
const heroe = { nombre: 'Tracer', rol: 'Daño', partidas: 120, victorias: 78 };
// nombre sale suelto; ...stats agrupa TODAS las demás propiedades en un objeto.
const { nombre, ...stats } = heroe;
// nombre → 'Tracer' stats → { rol: 'Daño', partidas: 120, victorias: 78 }
console.log(nombre);
// el resto del héroe, sin el nombre
console.log(stats);Un patrón muy real: quitar un campo de un objeto sin mutar. const { contraseña, ...publico } = usuario
te deja publico sin la contraseña, listo para enviar al cliente.
La regla que lo cierra: reparte o recoge según el lado#
Es el mismo ... en todos los ejemplos. Lo que cambia es dónde está:
Dónde está ... | Qué hace | Ejemplo |
|---|---|---|
A la derecha del =, dentro de [] o {} | Reparte (spread): copia/combina | [...a, ...b], { ...obj } |
Dentro de una llamada f(...) | Reparte (spread): despliega en argumentos | Math.max(...nums) |
| A la izquierda, en un destructuring | Recoge (rest): agrupa el resto | const [x, ...resto] = arr |
| En los parámetros de una función | Recoge (rest): agrupa argumentos | function f(a, ...resto) {} |
Spread reparte, rest recoge. Mismo símbolo, dos trabajos opuestos según el lado. Si
interiorizas eso, no vuelves a dudar de qué hace un ....
Pruébalo tú#
Edita el código y pulsa Ejecutar (o Ctrl+Enter) para ver la consola. Empieza por hacer que
el ascenso sume también una partida (partidas + 1), además de la victoria.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Qué hace const copia = { ...heroe, partidas: 99 }?
Tu turno#
Leerlo no es lo mismo que saber escribirlo. Resuélvelo aquí mismo: edita solucion.js para
ascender a un héroe sin mutarlo, fusionar dos plantillas y separar al capitán del resto, y muestra
los tres resultados por la consola. Cuando lo tengas (o si te atascas), despliega las soluciones y
fíjate en el salto de un nivel al siguiente.
Ejercicio · en esta página
Asciende, fusiona y separa con spread y rest
Sin mutar ningún dato de entrada: asciende a un héroe (copia con una partida y una victoria más), fusiona dos plantillas de equipo en una (gana la segunda en las claves repetidas) y separa al capitán del resto de la línea. Muestra los tres resultados por la consola.
Paso 1: Que funcione
- Asciende al héroe, fusiona las plantillas y separa al capitán.
- Los tres resultados se ven en la consola.
Paso 2: Que esté pulido
- Ascender devuelve una copia con spread, sin mutar el original.
- Fusionas las plantillas con spread de objetos.
- Separas el capitán con rest en el destructuring de array.
- Manejas el orden vacío sin romperte.
Paso 3: Que sea excelente
- Funciones puras con inmutabilidad total.
- Combinas spread y rest con destructuring en parámetros: la firma desempaqueta lo que cambia, el spread agrupa el resto.
- La fusión combina los titulares de ambas plantillas con spread de arrays, en vez de pisar el array entero.
Ver soluciones
// ════════════════════════════════════════════════════════════════════════════
// NIVEL OK — "que funcione"
//
// Resuelve las tres tareas de la forma más directa, aunque sea tosca:
// - Asciende al héroe MUTÁNDOLO (le suma en el propio objeto). Funciona, pero
// estropea el original: quien lo vuelva a leer ya no ve los datos de antes.
// - Fusiona las plantillas copiando campos a mano, propiedad por propiedad.
// - Separa el capitán con índices: orden[0] y orden.slice(1).
//
// Funciona y muestra los datos en la consola, que es el primer requisito. Sus
// límites los pule el nivel Mejor: la mutación es un foco de bugs y el copiado
// a mano se rompe en cuanto las plantillas tengan un campo más.
// ════════════════════════════════════════════════════════════════════════════
// Copia autocontenida de los datos para ejecutar esta solución suelta.
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
const plantillaBase = {
region: "Europa",
modo: "Competitivo",
maxJugadores: 6,
titulares: ["Mercy", "Reinhardt"],
};
const plantillaOverride = {
modo: "Amistoso",
titulares: ["Tracer", "Genji", "Ana"],
};
const ordenDePick = ["Mercy", "Reinhardt", "Tracer", "Genji", "Ana"];
// ─── 1) Ascender (MUTANDO: funciona pero estropea el original) ──────────────
function ascender(heroe) {
// modifica el objeto que nos pasaron
heroe.partidas = heroe.partidas + 1;
// ¡el original también cambia!
heroe.victorias = heroe.victorias + 1;
return heroe;
}
// ─── 2) Fusionar plantillas (copiando campo a campo) ────────────────────────
function fusionarPlantillas(base, override) {
// Copiamos a mano cada campo. Si mañana hay un campo nuevo, hay que añadirlo
// aquí o se pierde: frágil.
const resultado = {};
resultado.region = base.region;
resultado.maxJugadores = base.maxJugadores;
// override gana
resultado.modo = override.modo;
// override gana
resultado.titulares = override.titulares;
return resultado;
}
// ─── 3) Separar capitán (con índices) ───────────────────────────────────────
function separarCapitan(orden) {
// el primero
const capitan = orden[0];
// del índice 1 al final
const linea = orden.slice(1);
return { capitan: capitan, linea: linea };
}
// ─── Mostrar ─────────────────────────────────────────────────────────────────
function mostrar() {
const original = heroes[0];
// OJO: esto muta heroes[0]
const ascendido = ascender(heroes[0]);
const plantilla = fusionarPlantillas(plantillaBase, plantillaOverride);
const { capitan, linea } = separarCapitan(ordenDePick);
console.log("Ascenso:");
// nombre: partidas ascendidas, victorias ascendidas
console.log(
ascendido.nombre +
": " +
ascendido.partidas +
" partidas, " +
ascendido.victorias +
" victorias",
);
// original quedó igual porque lo mutamos: mismo objeto
console.log(
"(El original quedó en " +
original.partidas +
"/" +
original.victorias +
" porque lo mutamos: mismo objeto)",
);
console.log("Plantilla fusionada:");
// modo de override, titulares de override
console.log(
"Modo: " +
plantilla.modo +
" · Titulares: " +
plantilla.titulares.join(", "),
);
console.log("Alineación:");
// capitán y el resto de la línea
console.log("Capitán: " + capitan + " · Línea: " + linea.join(", "));
}
mostrar(); Por qué este nivel
- Resuelve las tres tareas de forma directa: asciende MUTANDO el héroe, fusiona copiando campo a campo y separa el capitán con índices y slice.
- Funciona y muestra los datos, que es el primer requisito.
- Sus límites: la mutación estropea el original (foco de bugs) y el copiado a mano se rompe en cuanto las plantillas crecen.
// ════════════════════════════════════════════════════════════════════════════
// NIVEL MEJOR — "que esté pulido"
//
// Por qué mejora a OK:
// - Ascender ya NO muta: devuelve una COPIA con spread ({ ...heroe, ... }) y
// pisa solo los dos campos que cambian. El original queda intacto, que es lo
// que React (y cualquier app con estado) espera de ti.
// - Fusionar plantillas con spread de objetos ({ ...base, ...override }): el
// segundo spread pisa las claves repetidas del primero. Si mañana hay un
// campo nuevo, se copia solo: nada de copiar a mano.
// - Separar capitán con REST en el destructuring de array
// ([capitan, ...linea]): una línea, sin slice ni índices.
// - Maneja el orden vacío sin reventar.
//
// Qué deja para Excelente: las piezas se pueden combinar con destructuring para
// quedar más declarativas, y el spread sirve también para combinar arrays de
// titulares de las dos plantillas en vez de que uno pise al otro entero.
// ════════════════════════════════════════════════════════════════════════════
// Copia autocontenida de los datos para ejecutar esta solución suelta.
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
const plantillaBase = {
region: "Europa",
modo: "Competitivo",
maxJugadores: 6,
titulares: ["Mercy", "Reinhardt"],
};
const plantillaOverride = {
modo: "Amistoso",
titulares: ["Tracer", "Genji", "Ana"],
};
const ordenDePick = ["Mercy", "Reinhardt", "Tracer", "Genji", "Ana"];
// ─── 1) Ascender (inmutable, con spread) ────────────────────────────────────
// Copia todos los campos del héroe y pisa partidas y victorias. El original
// no se toca: misma entrada → mismo objeto de entrada intacto.
function ascender(heroe) {
return {
// copia nombre, rol y todo lo demás
...heroe,
// pisa solo lo que cambia
partidas: heroe.partidas + 1,
victorias: heroe.victorias + 1,
};
}
// ─── 2) Fusionar plantillas (spread de objetos) ─────────────────────────────
// El segundo spread gana en las claves repetidas (modo, titulares).
function fusionarPlantillas(base, override) {
return { ...base, ...override };
}
// ─── 3) Separar capitán (rest en el destructuring) ──────────────────────────
// capitan recoge el primero; ...linea recoge TODO el resto en un array nuevo.
function separarCapitan(orden) {
// orden vacío
if (orden.length === 0) return { capitan: null, linea: [] };
const [capitan, ...linea] = orden;
return { capitan, linea };
}
// ─── Mostrar ─────────────────────────────────────────────────────────────────
function mostrar() {
const original = heroes[0];
// original NO cambia
const ascendido = ascender(original);
const plantilla = fusionarPlantillas(plantillaBase, plantillaOverride);
const { capitan, linea } = separarCapitan(ordenDePick);
console.log("Ascenso:");
// ascendido: partidas y victorias incrementadas
console.log(
ascendido.nombre +
": " +
ascendido.partidas +
" partidas, " +
ascendido.victorias +
" victorias",
);
// original sigue intacto porque no lo mutamos
console.log(
"(El original sigue en " +
original.partidas +
"/" +
original.victorias +
": no lo mutamos)",
);
console.log("Plantilla fusionada:");
// modo de override, titulares de override
console.log(
"Modo: " +
plantilla.modo +
" · Titulares: " +
plantilla.titulares.join(", "),
);
console.log("Alineación:");
// capitán y el resto de la línea
console.log("Capitán: " + capitan + " · Línea: " + linea.join(", "));
}
mostrar(); Por qué es mejor que el anterior
- Ascender ya NO muta: devuelve una copia con { ...heroe, ... } y pisa solo lo que cambia. El original queda intacto.
- Fusiona con spread de objetos ({ ...base, ...override }): el segundo gana en las claves repetidas, sin copiar a mano.
- Separa el capitán con rest en el destructuring ([capitan, ...linea]): una línea, sin slice.
- Maneja el orden vacío sin reventar.
// ════════════════════════════════════════════════════════════════════════════
// NIVEL EXCELENTE — "óptimo"
//
// Por qué mejora a Mejor:
// - Funciones puras y reutilizables: cada una depende solo de sus argumentos y
// devuelve siempre lo mismo, sin tocar nada de fuera. Inmutabilidad total.
// - Combina spread y rest CON destructuring (lo del capítulo anterior): la
// firma de ascender desempaqueta los campos que cambian (partidas, victorias)
// y el spread recoge el resto (...resto). El cuerpo queda declarativo.
// - La fusión es más fina: en vez de que override pise los titulares enteros,
// COMBINA los de ambas plantillas con spread de arrays en un array nuevo.
// Pisar un array entero pierde datos; combinarlos los conserva.
// - Regla mental aplicada en el mismo fichero: ... a la DERECHA reparte
// (spread), ... a la IZQUIERDA recoge (rest).
// ════════════════════════════════════════════════════════════════════════════
// Copia autocontenida de los datos para ejecutar esta solución suelta.
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
const plantillaBase = {
region: "Europa",
modo: "Competitivo",
maxJugadores: 6,
titulares: ["Mercy", "Reinhardt"],
};
const plantillaOverride = {
modo: "Amistoso",
titulares: ["Tracer", "Genji", "Ana"],
};
const ordenDePick = ["Mercy", "Reinhardt", "Tracer", "Genji", "Ana"];
// ─── 1) Ascender (puro, spread, sin recibir más de lo que usa) ──────────────
// Destructura en el parámetro para dejar claro qué campos cambia, y reparte el
// resto del héroe con spread. El original queda intacto.
function ascender({ partidas, victorias, ...resto }) {
// resto recoge nombre, rol y lo que venga (rest en destructuring de objeto).
return { ...resto, partidas: partidas + 1, victorias: victorias + 1 };
}
// ─── 2) Fusionar plantillas (combina titulares en vez de pisarlos) ──────────
// Separamos los titulares de cada plantilla con destructuring y los unimos con
// spread de arrays en un array nuevo. El resto de campos, spread normal.
function fusionarPlantillas(base, override) {
// rest en destructuring: titulares sueltos, restoBase recoge el resto del objeto
const { titulares: titularesBase = [], ...restoBase } = base;
// misma técnica para override
const { titulares: titularesOverride = [], ...restoOverride } = override;
// spread de arrays: une los titulares de ambas plantillas en uno nuevo
const titulares = [...titularesBase, ...titularesOverride];
// spread de objetos: restoBase + restoOverride + titulares combinados
return { ...restoBase, ...restoOverride, titulares };
}
// ─── 3) Separar capitán (rest puro) ─────────────────────────────────────────
// ...linea a la izquierda recoge el resto. Devolvemos una estructura cómoda.
function separarCapitan(orden) {
// defecto por si el orden viene vacío
const [capitan = null, ...linea] = orden;
return { capitan, linea };
}
// ─── Mostrar ─────────────────────────────────────────────────────────────────
function mostrar() {
const original = heroes[0];
// destructuring de la copia
const { nombre, partidas, victorias } = ascender(original);
const { modo, titulares } = fusionarPlantillas(
plantillaBase,
plantillaOverride,
);
const { capitan, linea } = separarCapitan(ordenDePick);
console.log("Ascenso:");
// nombre: partidas y victorias incrementadas
console.log(`${nombre}: ${partidas} partidas, ${victorias} victorias`);
// original intacto: función pura, sin mutar
console.log(
`(El original sigue en ${original.partidas}/${original.victorias}: función pura, sin mutar)`,
);
console.log("Plantilla fusionada:");
// modo y titulares combinados de ambas plantillas
console.log(`Modo: ${modo} · Titulares: ${titulares.join(", ")}`);
console.log("Alineación:");
// capitán y el resto de la línea
console.log(`Capitán: ${capitan} · Línea: ${linea.join(", ")}`);
}
mostrar(); Por qué es mejor que el anterior
- Funciones puras y reutilizables, con inmutabilidad total y sin efectos colaterales.
- Combina spread y rest CON destructuring: la firma desempaqueta lo que cambia, el cuerpo reparte el resto con spread de objeto.
- Fusión más fina: en vez de pisar el array de titulares entero con override, combina ambas listas con spread de arrays.
- Aplica la regla mental en el mismo fichero: ... a la derecha reparte, a la izquierda recoge.