Artículo original escrito por Oluwatobi Sofela
Artículo original JavaScript Rest vs Spread Operator – What’s the Difference?
Traducido y adaptado por Luis Ignacio Cabezas

JavaScript usa tres puntos (...) para ambos operadores, rest y spread. Pero estos dos operadores no son iguales.

La principal diferencia entre rest y spread es que el operador rest organiza el resto de algunos valores específicos suministrados por el usuario en un arreglo de JavaScript. Por otro lado la sintaxis spread expande los iterables en elementos individuales.

Por ejemplo, considere este código que utiliza rest para encerrar algunos valores en un arreglo:

// Usa rest para encerrar el resto de valores específicos proporcionados por el usuario en un arreglo:
function miBio(primerNombre, apellido, ...otraInfo) { 
  return otraInfo;
}

// Invoca la función miBio pasando cinco argumentos a sus parámetros:
miBio("Oluwatobi", "Sofela", "CodeSweetly", "Desarrollo Web", "Hombre");

// La invocación anterior devolverá:
["CodeSweetly", "Desarrollo Web", "Hombre"]

En el fragmento anterior, utilizamos el parámetro rest ...otraInfo para colocar "CodeSweetly", "Desarrollo Web", y "Hombre" en un arreglo.

Ahora, considere este ejemplo de un operador spread:

// Define una función con tres parámetros:
function miBio(primerNombre, apellido, empresa) { 
  return `${primerNombre} ${apellido} dirije ${empresa}`;
}

// Utiliza spread para expandir los elementos de un arreglo en argumentos individuales:
miBio(...["Oluwatobi", "Sofela", "CodeSweetly"]);

// La invocación anterior devolverá:
“Oluwatobi Sofela dirije CodeSweetly”

Pruebalo en StackBlitz

En el fragmento anterior, usamos el operador spread (...) para distribuir el contenido de ["Oluwatobi", "Sofela", "CodeSweetly"] entre los parámetros de miBio().

No te preocupes si aún no entiendes como funcionan los operadores rest y spread. ¡Este artículo te tiene cubierto!

En las siguientes secciones, discutiremos cómo funcionan rest y spread en JavaScript.

Así que, sin más preámbulos, empecemos con el operador rest.

¿Qué es exactamente el operador Rest?

El operador rest se utiliza para poner el resto de algunos valores específicos suministrados por el usuario en un arreglo de JavaScript.

Así, por ejemplo, esta es la sintaxis de rest:

...tusValores

Los tres puntos (...) en el fragmento anterior simbolizan el operador rest.

El texto que aparece después del operador rest hace referencia a los valores que desea incluir en un arreglo. Sólo se puede utilizar antes del último parámetro en la definición de una función.

Para entender mejor la sintaxis, veamos cómo funciona el operador rest con las funciones de JavaScript.

¿Cómo funciona el operador Rest en una función?

En las funciones de JavaScript, rest se utiliza como prefijo del último parámetro de la función.

He aquí un ejemplo:

// Defina una función con dos parámetros regulares y un parámetro rest:
function miBio(primerNombre, apellido, ...otraInfo) { 
  return otraInfo;
}

El operador rest (...) ordena al ordenador que añada cualquier argumento (otraInfo) suministrado por el usuario en un arreglo. Luego, lo asigna al parámetro otraInfo.

Como tal, llamamos a ...otraInfo un parámetro rest.

Nota: Los argumentos son valores opcionales que puedes pasar al parámetro de una función a través de un invocador.

Aquí hay otro ejemplo:

// Define una función con dos parámetros regulares y un parámetro rest:
function miBio(primerNombre, apellido, ...otraInfo) { 
  return otraInfo;
}

// Invoca la función miBio pasando cinco argumentos a sus parámetros:
miBio("Oluwatobi", "Sofela", "CodeSweetly", "Desarrollo Web", "Hombre");

// La invocación anterior devolverá:
["CodeSweetly", "Desarrollo Web", "Hombre"]

En el fragmento anterior, observa que la invocación de miBio pasó cinco argumentos a la función.

Es decir, "Oluwatobi" y "Sofela" se asignaron a los parámetros primerNombre y apellido.

Al mismo tiempo, el operador rest añadió los argumentos restantes ( "CodeSweetly", "Desarrollo Web", y "Hombre") en un arreglo y asignó ese arreglo al parámetro otraInfo.

Por lo tanto, la función miBio() devolvió correctamente ["CodeSweetly", "Desarrollo Web", "Hombre"]como contenido del parámetro otraInfo.

¡Cuidado! No puedes utilizar “use strict” dentro de una función que contenga un parámetro rest

Ten en cuenta que no puedes utilizar la directiva “use strict” dentro de ninguna función que contenga un parámetro rest, un parámetro por defecto o un parámetro de desestructuración. Si lo haces, el ordenador arrojará un error de sintaxis.

Por ejemplo, considere el siguiente ejemplo:

// Define una función con un parámetro rest:
function muestraMiNombre(..valor) {
  "use strict";
  return valor;
}

// La definición anterior devolverá:
"Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list"

muestraMiNombre() devuelve un error de sintaxis porque hemos utilizado la directiva “use strict” dentro de una función con un parámetro rest.

Pero supón que necesitas que tu función esté en modo estricto y al mismo tiempo utilizas el parámetro rest. En tal caso, puedes escribir la directiva “use strict” fuera de la función.

He aquí un ejemplo:

// Define una directiva "use strict" fuera de su función:
"use strict";

// Define una función con un parámetro rest:
function imprimirMiNombre(...value) {
  return value;
}

// Invoca la función imprimirMiNombre pasando dos argumentos a sus parámetros:
imprimirMiNombre("Oluwatobi", "Sofela");

// La invocación de arriba devolverá:
["Oluwatobi", "Sofela"]

Nota: Sólo coloca la directiva “use strict” fuera de su función si está bien que todo el script o el ámbito que lo encierra esté en modo estricto.

Así que ahora que sabemos cómo funciona rest en una función, podemos hablar de cómo funciona en una asignación de desestructuración.

Cómo funciona el operador rest en una asignación de desestructuración

El operador rest se utiliza normalmente como prefijo de la última variable de la asignación de desestructuración.

Este es un ejemplo:

// Define un arreglo desestructurado con dos variables regulares y una variable rest:
const [primerNombre, apellido, ...otraInfo] = [
  "Oluwatobi", "Sofela", "CodeSweetly", "Desarrollo Web", "Hombre"
];

// Invoca la variable otraInfo:
console.log(otraInfo); 

// La invocación anterior devolverá:
["CodeSweetly", "Desarrollo Web", "Hombre"]

El operador rest (...) indica a la computadora que añada el resto de los valores proporcionados por el usuario en un arreglo. Luego, asigna ese arreglo a la variable otraInfo.

Como tal, puedes llamar a ...otraInfo una variable rest.

Aquí hay otro ejemplo:

// Define un objeto de desestructuración con dos variables regulares y una variable rest:
const { primerNombre, apellido, ...otraInfo } = {
  primerNombre: "Oluwatobi",
  apellido: "Sofela", 
  nombreEmpresa: "CodeSweetly",
  profesion: "Desarrollo Web",
  genero: "Hombre"
}

// Invoca la variable otraInfo:
console.log(otraInfo);

// La invocación anterior devolverá:
{nombreEmpresa: "CodeSweetly", profesion: "Desarrollo Web", genero: "Hombre"}

En el fragmento anterior, observe que el operador rest asignó un objeto de propiedades - no un arreglo - a la variable otraInfo.

En otras palabras, siempre que utilice rest en un   objeto desestructurado, el operador rest producirá un objeto de propiedades.

Sin embargo, si se utiliza rest en un arreglo desestructurado o en una función, el operador producirá un arreglo.

Antes de terminar nuestra discusión sobre rest, deberías ser consciente de algunas diferencias entre los argumentos de JavaScript y el parámetro rest. Por lo tanto, vamos a hablar de eso a continuación.

Argumentos vs Parámetros rest: ¿Cuál es la diferencia?

Estas son algunas de las diferencias entre los argumentos de JavaScript y el parámetro rest:

Diferencia 1: El objeto argumentos es un objeto tipo arreglo — ¡no un arreglo real!

Tenga en cuenta que el objeto argumentos de JavaScript no es un arreglo real. En su lugar, es un objeto similar a un arreglo que no tiene las características completas de un arreglo normal de JavaScript.

El parámetro rest, sin embargo, es un arreglo real. Como tal, puedes utilizar todos los métodos de arreglos en él.

Así, por ejemplo, puedes llamar a los métodos sort(), map(), forEach(), o pop() en un parámetro rest. Pero no puedes hacer lo mismo con el objeto argumentos.

Diferencia 2: No se puede utilizar el objeto argumentos en una función de flecha

El objeto argumentos no está disponible dentro de una función de flecha, por lo que no se puede utilizar allí. Pero puedes usar el parámetro rest dentro de todas las funciones - incluyendo la función flecha.

Diferencia 3: Deja que rest sea tu preferencia

Es mejor utilizar los parámetros rest en lugar del objeto argumentos especialmente cuando se escribe código compatible con ES6.

Ahora que sabemos cómo funciona rest, vamos a discutir el operador spread para que podamos ver las diferencias.

¿Qué es el operador Spread y cómo funciona spread en JavaScript?

El operador spread (...) le ayuda a expandir los iterables en elementos individuales.

La sintaxis de spread funciona dentro de los literales de los arreglos, las llamadas a funciones y los objetos de propiedad inicializados para extender los valores de los objetos iterables en elementos separados. Así que, efectivamente, hace lo contrario que el operador rest.

Nota: Un operador spread es efectivo sólo cuando se usa dentro de literales de arreglo, llamadas a funciones u objetos de propiedades inicializadas.

Entonces, ¿qué significa exactamente esto? Veámoslo con algunos ejemplos.

Ejemplo de Spread 1: Cómo funciona Spread en un arreglo

const miNombre = ["Sofela", "es", "mi"];
const sobreMi = ["Oluwatobi", ...miNombre, "nombre."];

console.log(sobreMi);

// La invocación anterior devolverá:
[ "Oluwatobi", "Sofela", "es", "mi", "nombre." ]

El fragmento anterior utilizó la extensión (...) para copiar el arreglo miNombre en sobreMi.

Nota:

  • Las alteraciones de miNombre no se reflejarán en sobreMi porque todos los valores dentro de miNombre son primitivos. Por lo tanto, el operador spread simplemente copió y pegó el contenido de miNombre en sobreMi sin crear ninguna referencia al arreglo original.
  • El operador spread sólo hace una copia superficial. Así que, ten en cuenta que suponiendo que miNombre contuviera cualquier valor no primitivo, el ordenador habría creado una referencia entre miNombre y sobreMi.
  • Supongamos que no utilizamos la sintaxis de propagación para duplicar el contenido de miNombre. Por ejemplo, si hubiéramos escrito const sobreMi = ["Oluwatobi", miNombre, "nombre"]. En tal caso, el ordenador habría asignado una referencia a miNombre. Así, cualquier cambio realizado en el arreglo original se reflejaría en el duplicado.

Ejemplo de Spread 2: Cómo utilizar Spread para convertir una cadena en elementos individuales de un arreglo

const miNombre = "Oluwatobi Sofela";

console.log([...miNombre]);

// La invocación anterior devolverá:
[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]

En el fragmento anterior, hemos utilizado la sintaxis de propagación (...) dentro de un arreglo ([...]) para expandir el valor de la cadena de miNombre en elementos individuales.

Así, "Oluwatobi Sofela" se expandió en [ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ].

Ejemplo de Spread 3: Cómo funciona el operador Spread en una llamada a una función

const numeros = [1, 3, 5, 7];

function añadirNumeros(a, b, c, d) {
  return a + b + c + d;
}

console.log(añadirNumeros(...numeros));

// La invocación anterior devolverá:
16

En el fragmento anterior, hemos utilizado la sintaxis spread para repartir el contenido del arreglo de numeros entre los parámetros de añadirNumeros().

Supongamos que el arreglo de numeros tiene más de cuatro elementos. En tal caso, el ordenador sólo utilizará los cuatro primeros elementos como argumento de añadirNumeros() e ignorará el resto.

He aquí un ejemplo:

const numeros = [1, 3, 5, 7, 10, 200, 90, 59];

function añadirNumeros(a, b, c, d) {
  return a + b + c + d;
}

console.log(añadirNumeros(...numeros));

// La invocación anterior devolverá:
16

Aquí hay otro ejemplo:

const miNombre = "Oluwatobi Sofela";

function deletreaNombre(a, b, c) {
  return a + b + c;
}

console.log(deletreaNombre(...miNombre));      // devuelve: "Olu"

console.log(deletreaNombre(...miNombre[3]));   // devuelve: "wundefinedundefined"

console.log(deletreaNombre([...miNombre]));    // devuelve: "O,l,u,w,a,t,o,b,i, ,S,o,f,e,l,aundefinedundefined"

console.log(deletreaNombre({...miNombre}));    // devuelve: "[object Object]undefinedundefined"

Ejemplo de Spread 4: Como funciona Spread en un objeto

const misNombres = ["Oluwatobi", "Sofela"];
const bio = { ...misNombres, administra: "codesweetly.com" };

console.log(bio);

// La invocación anterior devolverá:
{ 0: "Oluwatobi", 1: "Sofela", administra: "codesweetly.com" }

En el fragmento anterior, hemos utilizado spread dentro del objeto bio para expandir los valores de miNombre en propiedades individuales.

Lo que hay que saber sobre el operador Spread

Ten en cuenta estos tres datos esenciales siempre que decidas utilizar el operador spread.

Información 1: Los operadores Spread no pueden expandir los valores de los objetos

Dado que un objeto no es un objeto iterable, no puedes utilizar el operador spread para expandir sus valores.

Sin embargo, puedes utilizar el operador spread para clonar propiedades de un objeto a otro.

He aquí un ejemplo:

const miNombre = { primerNombre: "Oluwatobi", apellido: "Sofela" };
const bio = { ...miNombre, sitioWeb: "codesweetly.com" };

console.log(bio);

// La invocación anterior devolverá:
{ primerNombre: "Oluwatobi", apellido: "Sofela", sitioWeb: "codesweetly.com" };

El fragmento anterior utiliza el operador spread para clonar el contenido de miNombre en el objeto bio.

Nota:

  • El operador spread sólo puede expandir los valores de los objetos iterables.
  • Un objeto es iterable sólo si él (o cualquier objeto en su cadena de prototipos) tiene una propiedad con una clave @@iterador.
  • Array, TypedArray, String, Map y Set son todos tipos iterables incorporados porque tienen la propiedad @@iterator por defecto.
  • Un objeto con propiedades no es un tipo de datos iterable porque no tiene la propiedad @@iterator por defecto.
  • Puedes hacer que un objeto con propiedades sea iterable añadiéndole @@iterator.

Información 2: El operador spread no clona propiedades idénticas

Supongamos que utilizas el operador spread para clonar propiedades del objeto A en el objeto B. Y supongamos que el objeto B contiene propiedades idénticas a las del objeto A. En tal caso, las versiones de B sustituirán a las de A.

He aquí un ejemplo:

const myNombre = { primerNombre: "Tobi", apellido: "Sofela" };
const bio = { ...miNombre, primerNombre: "Oluwatobi", sitioWeb: "codesweetly.com" };

console.log(bio);

// La invocación anterior devolverá:
{ primerNombre: "Oluwatobi", apellido: "Sofela", sitioWeb: "codesweetly.com" };

Observa que el operador spread no copió la propiedad primerNombre de miNombre en el objeto bio porque bio ya contiene una propiedad primerNombre.

Información 3: ¡Cuidado con el funcionamiento de spread cuando se utiliza en objetos que contienen no primitivos!

Supón que utilizas el operador spread en un objeto (o arreglo) que contiene sólo valores primitivos. El ordenador no creará ninguna referencia entre el objeto original y el duplicado.

Por ejemplo, considere este código de abajo:

const miNombre = ["Sofela", "es", "mi"];
const sobreMi = ["Oluwatobi", ...miNombre, "nombre."];

console.log(sobreMi);

// La invocación anterior devolverá:
["Oluwatobi", "Sofela", "es", "mi", "nombre."]

Observa que cada elemento de miNombre es un valor primitivo. Por lo tanto, cuando usamos el operador de propagación para clonar miNombre en sobreMi, el ordenador no creó ninguna referencia entre los dos arreglos.

Como tal, cualquier alteración que hagas a miNombre no se reflejará en sobreMi, y viceversa.

Como ejemplo, vamos a añadir más contenido a miNombre:

miNombre.push("verdadero");

Ahora, vamos a comprobar el estado actual de miNombre y de sobreMi:

console.log(myNombre); // ["Sofela", "es", "mi", "verdadero"]

console.log(sobreMi); // ["Oluwatobi", "Sofela", "es", "mi", "nombre."]

Observa que el contenido actualizado de miNombre no se refleja en sobreMi - porque spread no creó ninguna referencia entre el arreglo original y el duplicado.

¿Qué pasa si miNombre contiene elementos no primitivos?

Supongamos que miNombre contiene elementos no primitivos. En ese caso, spread creará una referencia entre el no primitivo original y el clonado.

He aquí un ejemplo:

const miNombre = [["Sofela", "es", "mi"]];
const sobreMi = ["Oluwatobi", ...miNombre, "nombre."];

console.log(sobreMi);

// La invocación anterior devolverá:
[ "Oluwatobi", ["Sofela", "es", "mi"], "nombre." ]

Observa que miNombre contiene un valor no primitivo.

Por lo tanto, el uso del operador spread para clonar el contenido de miNombre en sobreMi hizo que el ordenador creara una referencia entre los dos arreglos.

Como tal, cualquier alteración que hagas a la copia de miNombre se reflejará en la versión de sobreMi, y viceversa.

Como ejemplo, vamos a añadir más contenido a miNombre:

miNombre[0].push("verdadero");

Ahora, comprobemos el estado actual de miNombre y de sobreMi:

console.log(miNombre); // [["Sofela", "es", "mi", "verdadero"]]

console.log(sobreMi); // ["Oluwatobi", ["Sofela", "es", "mi", "verdadero"], "nombre."]

Observa que el contenido actualizado de miNombre se refleja en sobreMi - porque spread creó una referencia entre el arreglo original y el duplicado.

Aquí hay otro ejemplo:

const miNombre = { primerNombre: "Oluwatobi", apellido: "Sofela" };
const bio = { ...miNombre };

miNombre.primerNombre = "Tobi";

console.log(miNombre); // { primerNombre: "Tobi", apellido: "Sofela" }

console.log(bio); // { primerNombre: "Oluwatobi", apellido: "Sofela" }

En el fragmento anterior, la actualización de miNombre no se reflejó en bio porque utilizamos el operador de propagación en un objeto que contiene sólo valores primitivos.

Nota: Un desarrollador llamaría a miNombre un objeto superficial porque sólo contiene elementos primitivos.

Aquí hay un ejemplo más:

const miNombre = { 
  primerNombre: { primerNombre: "Oluwatobi", apellido: "Sofela" }
};

const bio = { ...miNombre };

miNombre.nombreCompleto.primerNombre = "Tobi";

console.log(miNombre); // { nombreCompleto: { primerNombre: "Tobi", apellido: "Sofela" } }

console.log(bio); // { NombreCompleto: { primerNombre: "Tobi", apellido: "Sofela" } }

En el fragmento anterior, la actualización de miNombre se refleja en bio porque hemos utilizado el operador spread en un objeto que contiene un valor no primitivo.

Nota:

  • Llamamos a miNombre un objeto profundo porque contiene un elemento no primitivo.
  • La copia superficial se realiza cuando se crean referencias al clonar un objeto en otro. Por ejemplo, ...miNombre produce una copia superficial del objeto miNombre porque cualquier alteración que hagas en uno se reflejará en el otro.
  • La copia profunda se realiza cuando se clonan objetos sin crear referencias. Por ejemplo, podría copiar profundamente miNombre en bio haciendo const bio = JSON.parse(JSON.stringify(miNombre)). Haciendo esto, el ordenador clonará miNombre en bio sin crear ninguna referencia.
  • Puedes romper la referencia entre los dos objetos reemplazando el objeto nombreCompleto dentro de miNombre o bio con un nuevo objeto. Por ejemplo, haciendo miNombre.nombreCompleto = { primerNombre: "Tobi", apellido: "Sofela" } desconectaría el puntero entre miNombre y bio.

Para terminar

En este artículo se han tratado las diferencias entre los operadores rest y spread. También hemos utilizado ejemplos para ver cómo funciona cada operador.

¡Gracias por leer!