learning-front

Nivel 3 · JavaScript moderno y asíncrono

Spread y rest

Recordatorio de spread (ya lo dominaste en el Nivel 2) y el protagonista nuevo: rest, el gemelo que recoge argumentos y restos sueltos en arrays y objetos.

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:

javascript
// ─── 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);
javascript
// ─── 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(', '));
javascript
// ─── 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.

javascript
// 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:

javascript
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:

javascript
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é haceEjemplo
A la derecha del =, dentro de [] o {}Reparte (spread): copia/combina[...a, ...b], { ...obj }
Dentro de una llamada f(...)Reparte (spread): despliega en argumentosMath.max(...nums)
A la izquierda, en un destructuringRecoge (rest): agrupa el restoconst [x, ...resto] = arr
En los parámetros de una funciónRecoge (rest): agrupa argumentosfunction 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.
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.