Spread y rest operators en JavaScript: tres puntos que lo cambian todo

Los tres puntos ... en JavaScript tienen dos roles distintos según el contexto: actúan como operador rest cuando recogen los argumentos sobrantes de una función o los elementos restantes de una desestructuración, y como operador spread cuando expanden un iterable en sus elementos individuales. Entender cuándo es cada uno evita errores frecuentes.

Rest parameters: recoger argumentos sobrantes

El operador rest en una definición de función agrupa todos los argumentos que no tienen parámetro explícito en un array real (no el objeto arguments):

// Función variádica: acepta cualquier número de argumentos
function sumar(...numeros) {
  return numeros.reduce((total, n) => total + n, 0);
}

console.log(sumar(1, 2, 3));       // 6
console.log(sumar(1, 2, 3, 4, 5)); // 15

// Mezclar parámetros normales con rest
function log(nivel, ...mensajes) {
  console.log(`[${nivel}]`, mensajes.join(' '));
}

log('INFO', 'Servidor', 'iniciado', 'en', 'puerto 3000');
// "[INFO] Servidor iniciado en puerto 3000"

// A diferencia de arguments, rest es un array real:
function ejemploRest(...args) {
  return args.map(x => x * 2);  // OK: args tiene .map()
}

function ejemploArguments() {
  return arguments.map(x => x * 2);  // Error: arguments no tiene .map()
}

Spread en llamadas a funciones

El operador spread expande un array (o cualquier iterable) en argumentos individuales de una función:

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

// Sin spread (antes había que usar apply):
console.log(Math.max.apply(null, numeros));  // 9

// Con spread:
console.log(Math.max(...numeros));  // 9

function saludar(nombre, saludo, puntuacion) {
  return `${saludo}, ${nombre}${puntuacion}`;
}

const args = ['Ana', 'Hola', '!'];
console.log(saludar(...args));  // "Hola, Ana!"

// Combinar argumentos normales con spread:
const extras = [4, 5];
console.log(sumar(1, 2, 3, ...extras));  // 15

Spread en arrays: clonar y fusionar

El spread es la forma idiomática moderna de clonar arrays y combinarlos sin mutar el original:

const original = [1, 2, 3];

// Clonar (copia superficial)
const copia = [...original];
copia.push(4);
console.log(original);  // [1, 2, 3] — no mutado
console.log(copia);     // [1, 2, 3, 4]

// Fusionar arrays
const a = [1, 2, 3];
const b = [4, 5, 6];
const fusionado = [...a, ...b];
console.log(fusionado);  // [1, 2, 3, 4, 5, 6]

// Insertar elementos en una posición
const inicio = [1, 2];
const fin = [5, 6];
const completo = [...inicio, 3, 4, ...fin];
console.log(completo);  // [1, 2, 3, 4, 5, 6]

// Convertir iterable a array
const set = new Set([1, 2, 2, 3, 3]);
const arraySinDuplicados = [...set];
console.log(arraySinDuplicados);  // [1, 2, 3]

Spread en objetos: clonar y fusionar

El spread en objetos (ES2018) permite copiar propiedades y combinar objetos de forma inmutable:

const base = { a: 1, b: 2, c: 3 };

// Clonar objeto (copia superficial)
const copia = { ...base };
copia.a = 99;
console.log(base.a);  // 1 — no mutado

// Fusionar objetos (el último gana en caso de conflicto)
const usuario = { nombre: 'Ana', rol: 'usuario', activo: true };
const cambios = { rol: 'admin', ultimoAcceso: '2026-06-26' };

const actualizado = { ...usuario, ...cambios };
console.log(actualizado);
// { nombre: 'Ana', rol: 'admin', activo: true, ultimoAcceso: '2026-06-26' }

// Patrón Redux: actualizar un campo sin mutar el estado
const estado = { usuario: 'Luis', contador: 0, cargando: false };
const nuevoEstado = { ...estado, contador: estado.contador + 1 };

La trampa de la copia superficial

Tanto con arrays como con objetos, el spread hace una copia superficial. Los objetos anidados se copian por referencia, no por valor:

const original = {
  nombre: 'Ana',
  direccion: { ciudad: 'Madrid', cp: '28001' }
};

const copia = { ...original };
copia.nombre = 'Luis';           // OK: tipo primitivo, independiente
copia.direccion.ciudad = 'Bcn';  // PROBLEMA: mismo objeto

console.log(original.nombre);           // 'Ana' — sin cambios
console.log(original.direccion.ciudad); // 'Bcn' — ¡mutado!

// Copia profunda de un nivel:
const copiaReal = {
  ...original,
  direccion: { ...original.direccion }
};

// Copia profunda general (no va bien con Dates, RegExp, etc.):
const copiaTotal = JSON.parse(JSON.stringify(original));

// O con la API moderna:
const copiaModerna = structuredClone(original);

La regla para recordar: los tres puntos antes de un parámetro en una función es rest (recoge). Los tres puntos antes de un valor es spread (expande). En una destructuración al final es rest; en cualquier otra posición es spread.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP