Artículo original: JavaScript Rest vs Spread Operator – What’s the Difference? por Oluwatobi Sofela

Traducido por: Keiler Guardo Herrera

JavaScript utiliza tres puntos (...) tanto para los operadores de rest como para propagación. Pero estos dos operadores no son lo mismo.

La principal diferencia entre rest y el operador propagación es que el operador rest pone el resto de algunos valores específicos suministrados por el usuario en un arreglo de JavaScript. Pero la progración expande los iterables en elementos individuales.

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

// Utiliza rest para encerrar el resto de los valores específicos suministrados por el usuario en un arreglo:
function miBiografia(primerNombre, apellido, ...otraInformacion) { 
  return otraInformacion;
}

// Llama la función miBiografia pasando cinco argumentos a sus parámetros:
miBiografia("Oluwatobi", "Sofela", "CodeSweetly", "Desarrollador Web", "Masculino");

// La llamada de arriba devolverá:
["CodeSweetly", "Desarrollador Web", "Masculino"]

Pruébalo en StackBlitz

En el ejemplo anterior, utilizamos el parámetro rest en ...otraInformacion para poner "CodeSweetly", "Desarrollador Web" y "Masculino" en un un arreglo.

Ahora, considera este ejemplo del operador de propagación:

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

// Utiliza propagación para expandir los elementos de un arreglo de elementos individuales:
miBiografia(...["Oluwatobi", "Sofela", "CodeSweetly"]);

// La llamada de arriba devolverá:
“Oluwatobi Sofela dirige CodeSweetly”

Pruébalo en StackBlitz

En el fragmento anterior, hemos utilizado el operador de propagación (...) para propagar el contenido de ["Oluwatobi", "Sofela", "CodeSweetly"] entre los parámetros de miBiografia().

No te preocupes si aún no entiendes el operador rest o el operador de propagación. ¡Este artículo lo tiene cubierto!

En las siguientes secciones, discutiremos cómo trabaja el operador rest y operador de propagación 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, aquí está 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 deseas 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 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 antepuesto del último parámetro de la función.

He aquí un ejemplo:

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

El operador rest (...) le indica al ordenador que añada cualquier argumento en otraInformacion suministrado por el usuario en un arreglo. A su vez, asigna ese arreglo al parámetro otraInformacion.

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

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

He aquí otro ejemplo:

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

// Llama la función miBiografia pasando cinco argumentos a sus parámetros:
miBiografia("Oluwatobi", "Sofela", "CodeSweetly", "Desarrollador Web", "Masculino");

// La invocación anterior devolverá:
["CodeSweetly", "Desarrollador Web", "Masculino"]

Pruébalo en StackBlitz

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

Es decir, "Oluwatobi" y "Sofela" fueron asignados a los parámetros primerNombre y apellido.

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

Por lo tanto, la función miBiografia() devolvió correctamente ["CodeSweetly", "Desarrollador Web", "Hombre"] como contenido del parámetro rest otraInformacion.

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

Tenga en cuenta que no puede utilizar la instrucción “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, or destructuring parameter. De lo contrario, el ordenador lanzará un error de sintaxis.

Por ejemplo, considere el siguiente ejemplo:

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

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

Pruébalo en CodeSandbox

imprimirMiNombre() devolvió un error de sintaxis porque se utilizó la instrucción “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 a la vez que utilizas el parámetro rest. En tal caso, escribir la instrucción “use strict” fuera de la función.

He aquí un ejemplo:

// Define la instrucción “use strict” fuera de tu función:
"use strict";

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

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

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

Pruébalo en CodeSandbox

Nota: Sólo coloca la instrucción “use strict” fuera de tu 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 suele utilizarse como prefijo de la última variable de la asignación de desestructuración.

He aquí un ejemplo:

// Define un arreglo de desestructuración con dos variables regulares y una variable rest:
const [primerNombre, apellido, ...otraInformacion] = [
  "Oluwatobi", "Sofela", "CodeSweetly", "Desarrollador Web", "Masculino"
];

// Consultamos la variable otraInformacion:
console.log(otraInformacion); 

// La llamada de arriba devolverá:
["CodeSweetly", "Desarrollador Web", "Masculino"]

Pruébalo en StackBlitz

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

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

Aquí hay otro ejemplo:

// Define un objeto de desestructuración con dos variables regulares y una variable res:
const { primerNombre, apellido, ...otraInformacion } = {
  primerNombre: "Oluwatobi",
  apellido: "Sofela", 
  nombreCompania: "CodeSweetly",
  profesion: "Desarrollador Web",
  genero: "Masculino"
}

// Llama a la variable otraInformacion:
console.log(otraInformacion);

// La llamada de arriba devolverá:
{nombreCompania: "CodeSweetly", profesion: "Desarrollador Web", genero: "Masculino"}

Pruébalo en StackBlitz

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

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 o función de desestructuración el operador producirá un arreglo literal.

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

Argumentos vs. parámetro 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 de argumentos es un objeto similar a un arreglo - ¡no un arreglo real!

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

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

Así, por ejemplo, puedes llamar los métodos sort(), map(), forEach(), o pop() Pero no puedes hacer lo mismo con el objeto de argumentos.

Diferencia 2: No se puede utilizar el objeto de 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 ahí. Pero puedes usar el parámetro rest dentro de todas las funciones - incluyendo la función flecha.

Diferencia 3: Deja que rest sea tu preferido

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

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

¿Qué es el operador de propagación y cómo funciona en JavaScript?

El operador de propagación (...) ayuda a expandir los iterables en elementos separados.

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

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

Entonces, ¿qué significa exactamente esto? Veamos con algunos ejemplos.

Ejemplo 1 de propagación: Cómo funciona la propagación en un arreglo literal

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

console.log(sobreMi);

// La llamada de arriba devolverá:
[ "Oluwatobi", "Sofela", "es", "mi", "nombre." ]

Pruébalo en StackBlitz

El fragmento anterior utilizó la expresión (...) para copiar el arreglo miNombre dentro de 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 de propagación simplemente copió y pegó el contenido de miNombre en sobreMi sin crear ninguna referencia al arreglo original.
  • Como mencionó @nombrekeff en un comentario aquí, el operador de propagación 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. Vea la información 3 un poco más abajo, para más información sobre cómo funciona el operador de propagación con valores primitivos y no primitivos.
  • 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 2 de propagación: Cómo utilizar propagación para convertir una cadena en elementos individuales de un arreglo

const miNombre = "Oluwatobi Sofela";

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

// La llamada de arriba devolverá:
[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]

Pruébalo en StackBlitz

En el fragmento anterior, hemos utilizado la sintaxis de propagación (...) dentro de un arreglo literal ([...]) 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 3: Cómo funciona el operador de propagación en una llamada de función

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

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

console.log(agregarNumeros(...numeros));

// La llamada de arriba devolverá:
16

Pruébalo en StackBlitz

En el fragmento anterior, hemos utilizado la sintaxis de propagación para distribuir el contenido de un arreglo de numeros entre los parámetros de agregarNumeros().

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 agregarNumeros() e ignorará el resto.

He aquí un ejemplo:

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

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

console.log(agregarNumeros(...numeros));

// La llamada de arriba devolverá:
16

Pruébalo en StackBlitz

He aquí otro ejemplo:

const miNombre = "Oluwatobi Sofela";

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

console.log(deletrearNombre(...miNombre));      // devolverá: "Olu"

console.log(deletrearNombre(...miNombre[3]));   // devolverá: "wundefinedundefined"

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

console.log(deletrearNombre({...miNombre}));    // devolverá: "[object Object]undefinedundefined"

Pruébalo en StackBlitz

Ejemplo 4 de propagación: Cómo funciona la propagación en un objeto literal

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

console.log(biografia);

// La llamada de arriba devolverá:
{ 0: "Oluwatobi", 1: "Sofela", dirige: "codesweetly.com" }

Pruébalo en StackBlitz

En el fragmento anterior, hemos utilizado la propagación dentro del objeto biografia para expandir los valores de misNombres en propiedades individuales.

Lo que hay que saber sobre el operador de propagación

Tenga en cuenta estos tres datos esenciales siempre que decida utilizar el operador de propagación.

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

Dado que un objecto de propiedades no es un objeto iterable, no puedes utilizar el operador de propagación para expandir sus valores.

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

He aquí un ejemplo:

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

console.log(biografia);

// La llamada de arriba retornará:
{ primerNombre: "Oluwatobi", apellido: "Sofela", sitioWeb: "codesweetly.com" };

Pruébalo en StackBlitz

El fragmento anterior utilizó el operador de propagación para clonar el contenido de miNombre en el objeto biografia.

Nota:

  • El operador de propagación 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 @@iterator.
  • Array, TypedArray, String, Map, y Set son todos tipos iterables incorporados porque tienen la propiedad @@iterator por defecto.
  • Un objeto no es un tipo de datos iterable porque no tiene la propiedad @@iterator por defecto.
  • Puedes hacer que un objeto sea iterable añadiéndole @@iterator.

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

Supón que utilizas el operador de propagación para clonar propiedades del objeto A en el objeto B. Y supón que el objeto B contiene propiedades idénticas a las del objeto A. En tal caso, las versiones de B anularán las de A.

He aquí un ejemplo:

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

console.log(biografia);

// La llamada de arriba devolverá:
{ primerNombre: "Oluwatobi", apellido: "Sofela", sitioWeb: "codesweetly.com" };

Pruébalo en StackBlitz

Observe que el operador de propagación no ha copiado la propiedad miNombre de primerNombre en el objeto biografia porque biografia ya contiene una propiedad primerNombre.

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

Supón que utilizas el operador propagación 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, considera este código de abajo:

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

console.log(sobreMi);

// La llamada de arriba devolverá:
["Oluwatobi", "Sofela", "es", "mi", "nombre."]

Pruébalo en StackBlitz

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

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

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

Pruébalo en StackBlitz

Observe que el contenido actualizado de miNombre no se reflejó en sobreMi — porque la propagación 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, la propagación creará una referencia entre el original no primitivo y el clonado.

He aquí un ejemplo:

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

console.log(sobreMi);

// La llamada de arriba retornará:
[ "Oluwatobi", ["Sofela", "es", "mi"], "nombre." ]

Pruébalo en StackBlitz

Observe que miNombre contiene un valor no primitivo .

Por lo tanto, el uso del operador de propagación 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 haga en 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, vamos a comprobar el estado actual de miNombre y sobreMi:

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

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

Pruébalo en StackBlitz

Observe que el contenido actualizado de miNombre se refleja en sobreMi — porque la propagación creó una referencia entre el arreglo original y el duplicado.

Aquí hay otro ejemplo:

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

miNombre.primerNombre = "Tobi";

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

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

Pruébalo en StackBlitz

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

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

Aquí hay un ejemplo más:

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

const biografia = { ...miNombre };

miNombre.nombreCompleto.primerNombre = "Tobi";

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

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

Pruébalo en StackBlitz

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

Nota:

  • Llamamos a miNombre objeto profundo porque contiene un elemento no primitivo.
  • La copia profunda se realiza cuando se crean referencias al clonar un objeto en otro. Por ejemplo, ...miNombre produce una copia profunda 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 miNombre en biografia haciendo const biografia = JSON.parse(JSON.stringify(miNombre)). Haciendo esto, el ordenador clonará miNombre en biografia sin crear ninguna referencia.
  • Puedes romper la referencia entre los dos objetos reemplazando el objeto nombreCompleto dentro de miNombre o biografia con un nuevo objeto. Por ejemplo, haciendo miNombre.nombreCompleto = { primerNombre: "Tobi", apellido: "Sofela" } desconectaría el puntero entre miNombre y biografia.

Para terminar

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

Gracias por leerlo!