Spread operator
Introducción
El operador spread (o de propagación), representado por tres puntos suspensivos (...
), es una característica poderosa introducida en ES6 (ECMAScript 2015) que nos permite expandir elementos iterables como arrays, strings, o objetos en lugares donde se esperan múltiples elementos. Este operador complementa perfectamente la desestructuración que vimos en el artículo anterior y nos ofrece una sintaxis elegante y concisa para muchas operaciones comunes en JavaScript, como combinar arrays, copiar objetos, o pasar múltiples argumentos a funciones. El operador spread se ha convertido en una herramienta imprescindible en el desarrollo moderno con JavaScript, simplificando tareas que antes requerían código más verbose y propenso a errores.
Sintaxis y funcionamiento del operador spread (...)
La sintaxis del operador spread es simple: tres puntos suspensivos (...
) seguidos de un iterable (array, string) o un objeto. Lo que hace es "expandir" los elementos, caracteres o propiedades en el contexto donde se utiliza.
La forma general es:
// Para arrays u otros iterables
const nuevoArray = [...iterableExistente, elementosAdicionales];
// Para objetos
const nuevoObjeto = {...objetoExistente, propiedadesAdicionales};
Uso con arrays
El operador spread permite extraer los elementos de un array y colocarlos individualmente en otro contexto.
Combinar arrays
const frutasVerano = ["sandía", "melón", "melocotón"];
const frutasOtoño = ["manzana", "pera", "uva"];
// Método antiguo (concat)
const todasLasFrutas1 = frutasVerano.concat(frutasOtoño);
// Con operador spread
const todasLasFrutas2 = [...frutasVerano, ...frutasOtoño];
console.log(todasLasFrutas2);
// ["sandía", "melón", "melocotón", "manzana", "pera", "uva"]
Incluso podemos insertar elementos adicionales al combinar:
const frutasInvierno = ["naranja", "mandarina", "limón"];
const todasLasFrutas = [
...frutasVerano,
"plátano", // elemento individual
...frutasOtoño,
...frutasInvierno
];
console.log(todasLasFrutas);
// ["sandía", "melón", "melocotón", "plátano", "manzana", "pera", "uva", "naranja", "mandarina", "limón"]
Crear copias de arrays
El operador spread proporciona una forma sencilla de crear copias superficiales de arrays:
const original = [1, 2, 3, 4, 5];
const copia = [...original];
// Modificamos la copia sin afectar al original
copia.push(6);
console.log(original); // [1, 2, 3, 4, 5]
console.log(copia); // [1, 2, 3, 4, 5, 6]
Nota importante: El operador spread realiza una copia superficial (shallow copy). Esto significa que para arrays con objetos anidados o arrays multidimensionales, los elementos internos seguirán siendo referencias al objeto original.
const arrayConObjetos = [
{ id: 1, nombre: "Juan" },
{ id: 2, nombre: "Ana" }
];
const copiaArray = [...arrayConObjetos];
// Modificar un objeto dentro de la copia afecta al original
copiaArray[0].nombre = "Pedro";
console.log(arrayConObjetos[0].nombre); // "Pedro"
Convertir un string en array de caracteres
const palabra = "Hola";
const caracteres = [...palabra];
console.log(caracteres); // ["H", "o", "l", "a"]
Combinar con el operador rest en desestructuración
Aunque comparten la misma sintaxis (...
), el operador spread y el operador rest tienen propósitos opuestos:
// Rest (agrupar) en desestructuración
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];
console.log(primero); // 1
console.log(segundo); // 2
console.log(resto); // [3, 4, 5]
// Spread (expandir) al crear un nuevo array
const nuevoArray = [...resto, 6, 7];
console.log(nuevoArray); // [3, 4, 5, 6, 7]
Uso con objetos
A partir de ES2018 (ES9), el operador spread también se puede utilizar con objetos, permitiéndonos expandir las propiedades de un objeto en otro.
Combinar objetos
const datosPersonales = {
nombre: "Sara",
apellido: "López",
edad: 32
};
const datosProfesionales = {
profesion: "desarrolladora",
empresa: "TechSolutions",
experiencia: 5
};
// Combinación de objetos
const perfilCompleto = {
...datosPersonales,
...datosProfesionales,
ubicacion: "Madrid" // Propiedad adicional
};
console.log(perfilCompleto);
/*
{
nombre: "Sara",
apellido: "López",
edad: 32,
profesion: "desarrolladora",
empresa: "TechSolutions",
experiencia: 5,
ubicacion: "Madrid"
}
*/
Resolver conflictos de propiedades
Cuando hay propiedades con el mismo nombre, el último valor sobrescribe a los anteriores:
const configuracionPredeterminada = {
tema: "claro",
fuente: "Arial",
tamaño: 12,
notificaciones: true
};
const preferenciasUsuario = {
tema: "oscuro",
tamaño: 14
};
// Las preferencias del usuario sobrescriben los valores predeterminados
const configuracionFinal = {
...configuracionPredeterminada,
...preferenciasUsuario
};
console.log(configuracionFinal);
/*
{
tema: "oscuro",
fuente: "Arial",
tamaño: 14,
notificaciones: true
}
*/
El orden es importante. Si invirtiéramos el orden de los objetos en el spread, los valores predeterminados sobrescribirían las preferencias del usuario.
Crear copias de objetos
El operador spread nos permite crear copias superficiales de objetos:
const producto = {
id: "P001",
nombre: "Teclado mecánico",
precio: 89.99,
disponible: true
};
const copia = {...producto};
// Modificamos la copia sin afectar al original
copia.disponible = false;
copia.precio = 79.99;
console.log(producto.disponible); // true
console.log(producto.precio); // 89.99
console.log(copia.disponible); // false
console.log(copia.precio); // 79.99
Al igual que con los arrays, es importante recordar que el operador spread realiza una copia superficial, por lo que las propiedades anidadas mantienen la referencia al objeto original:
const usuario = {
nombre: "Miguel",
contacto: {
email: "miguel@ejemplo.com",
telefono: "612345678"
}
};
const copiaUsuario = {...usuario};
// Modificar un objeto anidado afecta al original
copiaUsuario.contacto.email = "miguel.nuevo@ejemplo.com";
console.log(usuario.contacto.email); // "miguel.nuevo@ejemplo.com"
Añadir o actualizar propiedades específicas
El operador spread también es útil para crear un nuevo objeto con algunas propiedades modificadas o añadidas:
const producto = {
id: "P002",
nombre: "Monitor 27\"",
precio: 249.99
};
// Crear nuevo objeto con precio reducido y propiedad adicional
const productoOferta = {
...producto,
precio: 199.99,
descuento: "20%"
};
console.log(productoOferta);
/*
{
id: "P002",
nombre: "Monitor 27\"",
precio: 199.99,
descuento: "20%"
}
*/
Pasar argumentos a funciones
Una de las aplicaciones más útiles del operador spread es pasar los elementos de un array como argumentos individuales a una función.
Uso con Math.max/min
const numeros = [5, 18, 3, 12, 7];
// Antes del operador spread
const maximo1 = Math.max.apply(null, numeros);
// Con operador spread
const maximo2 = Math.max(...numeros);
console.log(maximo2); // 18
Funciones con número variable de argumentos
function sumar(...numeros) {
return numeros.reduce((total, num) => total + num, 0);
}
const valores = [10, 20, 30, 40];
// Llamar a la función y expandir el array como argumentos
const resultado = sumar(...valores);
console.log(resultado); // 100
Combinación con argumentos específicos
function crearMensaje(destinatario, remitente, ...lineasContenido) {
return {
para: destinatario,
de: remitente,
contenido: lineasContenido.join('\n')
};
}
const lineas = [
"Espero que este mensaje te encuentre bien.",
"Te escribo para informarte sobre los cambios recientes.",
"Saludos cordiales."
];
const mensaje = crearMensaje("ana@ejemplo.com", "carlos@ejemplo.com", ...lineas);
console.log(mensaje);
/*
{
para: "ana@ejemplo.com",
de: "carlos@ejemplo.com",
contenido: "Espero que este mensaje te encuentre bien.\nTe escribo para informarte sobre los cambios recientes.\nSaludos cordiales."
}
*/
Clonación de estructuras
El operador spread es una herramienta útil para crear copias de estructuras de datos, aunque debemos entender sus limitaciones.
Clonación superficial vs. profunda
Como hemos mencionado anteriormente, el operador spread realiza una clonación superficial (shallow clone):
// Clonación superficial de objetos anidados
const original = {
nombre: "Proyecto X",
fechas: {
inicio: "2023-01-15",
fin: "2023-06-30"
}
};
const copia = {...original};
// Cambiar una propiedad de nivel superior
copia.nombre = "Proyecto Y";
console.log(original.nombre); // "Proyecto X" (no se ve afectado)
// Cambiar una propiedad anidada
copia.fechas.inicio = "2023-02-01";
console.log(original.fechas.inicio); // "2023-02-01" (¡se ve afectado!)
Para realizar una clonación profunda (deep clone), necesitamos otras técnicas:
// Usando JSON (con limitaciones)
const copiaCompleta = JSON.parse(JSON.stringify(original));
// Ahora podemos modificar propiedades anidadas sin afectar al original
copiaCompleta.fechas.fin = "2023-07-15";
console.log(original.fechas.fin); // "2023-06-30" (no se ve afectado)
Nota: El método de JSON tiene limitaciones importantes: no puede clonar funciones, objetos Date, RegExp, Map, Set, etc. Para estos casos, es mejor usar bibliotecas específicas o implementar una función personalizada de clonación profunda.
Combinación con desestructuración
El operador spread se combina de forma elegante con la desestructuración:
const estadisticas = {
maximo: 98,
minimo: 15,
promedio: 68.5,
mediana: 72,
desviacion: 12.8,
moda: 75
};
// Extraer algunas propiedades y agrupar el resto
const { maximo, minimo, ...otrasEstadisticas } = estadisticas;
console.log(maximo); // 98
console.log(minimo); // 15
console.log(otrasEstadisticas);
/*
{
promedio: 68.5,
mediana: 72,
desviacion: 12.8,
moda: 75
}
*/
Casos de uso comunes
Implementar patrones de inmutabilidad
El operador spread es fundamental en paradigmas de programación funcional e inmutable, como los utilizados en React/Redux:
// Actualizar estado inmutable
function reducerTareas(estado = [], accion) {
switch (accion.tipo) {
case 'AÑADIR_TAREA':
// Crear nuevo array sin modificar el original
return [...estado, accion.tarea];
case 'ACTUALIZAR_TAREA':
// Crear nuevo array con un elemento actualizado
return estado.map(tarea =>
tarea.id === accion.id ? {...tarea, ...accion.cambios} : tarea
);
default:
return estado;
}
}
Fusionar datos de diferentes fuentes
function combinarDatosUsuario(datosBasicos, datosExtra, preferencias) {
return {
id: datosBasicos.id,
...datosBasicos.perfil,
...datosExtra,
configuracion: {
...preferencias.opciones
}
};
}
const usuario = combinarDatosUsuario(
{ id: "U123", perfil: { nombre: "Eva", email: "eva@ejemplo.com" } },
{ edad: 27, ciudad: "Barcelona" },
{ opciones: { tema: "oscuro", idioma: "es" } }
);
console.log(usuario);
/*
{
id: "U123",
nombre: "Eva",
email: "eva@ejemplo.com",
edad: 27,
ciudad: "Barcelona",
configuracion: {
tema: "oscuro",
idioma: "es"
}
}
*/
Eliminar propiedades no deseadas
Combinando desestructuración y spread, podemos crear un nuevo objeto sin ciertas propiedades:
const datosFormulario = {
nombre: "Antonio",
email: "antonio@ejemplo.com",
contraseña: "abc123xyz",
confirmarContraseña: "abc123xyz",
_token: "a89f7d6e5c4b3a2"
};
// Extraer las propiedades que queremos eliminar y guardar el resto
const { contraseña, confirmarContraseña, _token, ...datosFiltrados } = datosFormulario;
console.log(datosFiltrados);
/*
{
nombre: "Antonio",
email: "antonio@ejemplo.com"
}
*/
Convertir estructuras iterables a arrays
// Convertir un conjunto (Set) a array
const conjuntoNumeros = new Set([1, 2, 3, 2, 4, 1, 5]);
const arrayNumeros = [...conjuntoNumeros];
console.log(arrayNumeros); // [1, 2, 3, 4, 5]
// Convertir mapa (Map) a array de pares
const mapaUsuarios = new Map([
[1, "Ana"],
[2, "Bernardo"],
[3, "Carmen"]
]);
const arrayUsuarios = [...mapaUsuarios];
console.log(arrayUsuarios); // [[1, "Ana"], [2, "Bernardo"], [3, "Carmen"]]
// Convertir arguments a array (en funciones)
function ejemploArguments() {
const args = [...arguments];
return args.map(arg => typeof arg);
}
console.log(ejemploArguments(1, "hola", true, {})); // ["number", "string", "boolean", "object"]
Limitaciones y consideraciones
Rendimiento
El operador spread puede afectar al rendimiento cuando se utiliza con objetos o arrays muy grandes, ya que crea nuevas copias:
// Con arrays pequeños no hay problema
const pequeño = [...Array(10).keys()];
// Con arrays muy grandes puede impactar el rendimiento
const grande = [...Array(1000000).keys()]; // Puede ser lento
Anidamiento profundo
Como hemos visto, el operador spread solo realiza copias superficiales:
const complejo = {
nivel1: {
nivel2: {
nivel3: {
datos: [1, 2, 3]
}
}
}
};
// El spread solo clona el primer nivel
const copia = {...complejo};
Propiedades no enumerables
El operador spread solo copia propiedades enumerables, ignorando aquellas que no lo son:
const obj = Object.create({}, {
visible: {
value: "Propiedad enumerable",
enumerable: true
},
invisible: {
value: "Propiedad no enumerable",
enumerable: false
}
});
const copia = {...obj};
console.log(copia.visible); // "Propiedad enumerable"
console.log(copia.invisible); // undefined
Resumen
El operador spread (...
) es una característica poderosa y versátil de JavaScript moderno que nos permite:
- Expandir los elementos de un array o las propiedades de un objeto en nuevas estructuras
- Crear copias superficiales de arrays y objetos
- Combinar múltiples arrays u objetos de manera concisa
- Pasar arrays como argumentos individuales a funciones
- Implementar patrones de programación inmutable
- Trabajar de forma eficiente con estructuras de datos
A pesar de sus limitaciones (copias superficiales y rendimiento con grandes estructuras), el operador spread se ha convertido en una herramienta imprescindible para el desarrollo moderno de JavaScript, facilitando la manipulación de datos de forma elegante y expresiva.
Junto con características como la desestructuración, las funciones flecha y las plantillas de cadena, el operador spread forma parte del conjunto de herramientas que han hecho que el código JavaScript moderno sea más conciso, expresivo y fácil de mantener.
En el próximo artículo, exploraremos JSON (JavaScript Object Notation), un formato ligero de intercambio de datos, y cómo trabajar con él a través de los métodos JSON.stringify()
y JSON.parse()
.