Original article: https://www.freecodecamp.org/news/javascript-rest-vs-spread-operators/
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 ensobreMi
porque todos los valores dentro demiNombre
son primitivos. Por lo tanto, el operador de propagación simplemente copió y pegó el contenido demiNombre
ensobreMi
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 entremiNombre
ysobreMi
. 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 escritoconst sobreMi = ["Oluwatobi", miNombre, "nombre."]
. En tal caso, el ordenador habría asignado una referencia amiNombre
. 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 objetomiNombre
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
enbiografia
haciendoconst biografia = JSON.parse(JSON.stringify(miNombre))
. Haciendo esto, el ordenador clonarámiNombre
enbiografia
sin crear ninguna referencia. - Puedes romper la referencia entre los dos objetos reemplazando el objeto
nombreCompleto
dentro demiNombre
obiografia
con un nuevo objeto. Por ejemplo, haciendomiNombre.nombreCompleto = { primerNombre: "Tobi", apellido: "Sofela" }
desconectaría el puntero entremiNombre
ybiografia
.
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!