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.
