preloader

Parámetros Rest y Operador Spread en JavaScript

Parámetros Rest y Operador Spread

Muchas funciones nativas de JavaScript permiten trabajar con un número indeterminado de argumentos.

Por ejemplo:

  • Math.max(arg1, arg2, ..., argN) – devuelve el valor más alto entre los argumentos.
  • Object.assign(dest, src1, ..., srcN) – copia las propiedades de src1..N en dest. … y otras más.

En este capítulo, aprenderemos cómo implementar lo mismo, así como a manejar estas funciones y arrays de manera efectiva.

Parámetros Rest

Una función puede ser llamada con cualquier cantidad de argumentos, sin importar cómo esté definida.

Por ejemplo:

				
					function sum(a, b) {
  return a + b;
}

console.log(sum(1, 2, 3, 4, 5)); // Salida: 3

				
			

No habrá error por tener «demasiados» argumentos. Sin embargo, solo se considerarán los dos primeros, por lo que el resultado será 3.

Los parámetros restantes pueden ser agrupados en un array utilizando tres puntos (...) seguidos por el nombre del array que los contendrá. Esto literalmente significa «recoger los parámetros restantes en un array».

Por ejemplo, para agrupar todos los parámetros en un array args:

				
					function sumAll(...args) { // args es el nombre del array
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

console.log(sumAll(1)); // 1
console.log(sumAll(1, 2)); // 3
console.log(sumAll(1, 2, 3)); // 6

				
			

Podemos optar por obtener los primeros parámetros como variables y agrupar solo el resto.

Aquí los primeros dos argumentos se asignan a variables y el resto se agrupa en el array titles:

				
					function showName(firstName, lastName, ...titles) {
  console.log(firstName + ' ' + lastName); // Julio Cesar

  // el resto va en el array titles
  // por ejemplo titles = ["Cónsul", "Emperador"]
  console.log(titles[0]); // Cónsul
  console.log(titles[1]); // Emperador
  console.log(titles.length); // 2
}

showName("Julio", "Cesar", "Cónsul", "Emperador");

				
			

Los parámetros rest deben ir al final

Los parámetros rest recogen todos los argumentos restantes, por lo que el siguiente código no tiene sentido y causa un error:

				
					function f(arg1, ...rest, arg2) { // arg2 después de ...rest ?!
  // error
}

				
			

...rest debe ir siempre al final.

La variable “arguments”

También existe un objeto similar a un array llamado arguments que contiene todos los argumentos indexados.

Por ejemplo:

				
					function showName() {
  console.log(arguments.length);
  console.log(arguments[0]);
  console.log(arguments[1]);

  // arguments es iterable
  // for(let arg of arguments) console.log(arg);
}

// muestra: 2, Julio, Cesar
showName("Julio", "Cesar");

// muestra: 1, Ilya, undefined (no hay segundo argumento)
showName("Ilya");

				
			

Antiguamente, los parámetros rest no existían en el lenguaje, y usar arguments era la única manera de obtener todos los argumentos de una función. Aún funciona, y podemos encontrarlo en código antiguo.

Pero la desventaja es que, aunque arguments es similar a un array e iterable, no es un array real. No soporta los métodos de array, no podemos ejecutar arguments.map(...), por ejemplo.

Además, siempre contiene todos los argumentos. No podemos capturarlos parcialmente como hicimos con los parámetros rest.

Por lo tanto, cuando necesitemos estas funcionalidades, los parámetros rest son preferidos.

Las funciones flecha no poseen «arguments»

Si accedemos al objeto arguments desde una función flecha, toma su valor de la función «normal» externa.

Aquí hay un ejemplo:

				
					function f() {
  let showArg = () => console.log(arguments[0]);
  showArg();
}

f(1); // 1

				
			

Como recordamos, las funciones de flecha no tienen su propio this. Ahora sabemos que tampoco tienen el objeto especial arguments.

Sintaxis Spread

Acabamos de ver cómo obtener un array de la lista de parámetros.

Pero a veces necesitamos hacer exactamente lo contrario.

Por ejemplo, existe una función nativa Math.max que devuelve el número más grande de una lista:

				
					console.log(Math.max(3, 5, 1)); // 5

				
			

Ahora bien, supongamos que tenemos un array [3, 5, 1]. ¿Cómo ejecutamos Math.max con él?

Pasar la variable no funcionará, porque Math.max espera una lista de argumentos numéricos, no un único array:

				
					let arr = [3, 5, 1];

console.log(Math.max(arr)); // NaN

				
			

Y seguramente no podremos listar manualmente los ítems en el código Math.max(arr[0], arr[1], arr[2]), porque tal vez no sepamos cuántos son. A medida que nuestro script se ejecuta, podría haber muchos elementos, o podría no haber ninguno. Y eso podría ponerse feo.

¡Operador Spread al rescate! Es similar a los parámetros rest, también usa ..., pero hace exactamente lo opuesto.

Cuando ...arr es usado en el llamado de una función, «expande» el objeto iterable arr en una lista de argumentos.

Para Math.max:

				
					let arr = [3, 5, 1];

console.log(Math.max(...arr)); // 5 (spread convierte el array en una lista de argumentos)

				
			

También podemos pasar múltiples iterables de esta manera:

				
					let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

console.log(Math.max(...arr1, ...arr2)); // 8

				
			

Incluso podemos combinar el operador spread con valores normales:

				
					let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

console.log(Math.max(1, ...arr1, 2, ...arr2, 25)); // 25

				
			

Además, el operador spread puede ser usado para combinar arrays:

				
					let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [0, ...arr, 2, ...arr2];

console.log(merged); // 0,3,5,1,2,8,9,15 (0, luego arr, después 2, después arr2)

				
			

En los ejemplos anteriores utilizamos un array para demostrar el operador spread, pero cualquier iterable funcionará también.

Por ejemplo, aquí usamos el operador spread para convertir una cadena en un array de caracteres:

				
					let str = "Hola";

console.log([...str]); // H,o,l,a

				
			

El operador spread utiliza internamente iteradores para iterar los elementos, de la misma manera que for..of hace.

Entonces, para una cadena, for..of retorna caracteres y ...str se convierte en «H»,»o»,»l»,»a». La lista de caracteres es pasada a la inicialización del array [...].

Para esta tarea en particular, también podríamos haber usado Array.from, ya que convierte un iterable (como una cadena de caracteres) en un array:

				
					let str = "Hola";

// Array.from convierte un iterable en un array
console.log(Array.from(str)); // H,o,l,a

				
			

El resultado es el mismo que [...str].

Pero hay una sutil diferencia entre Array.from(obj) y [...obj]:

  • Array.from opera con símil-arrays e iterables.
  • El operador spread solo opera con iterables.

Por lo tanto, para la tarea de convertir algo en un array, Array.from tiende a ser más universal.

Copia de un objeto array

¿Recuerdas cuando hablamos acerca de Object.assign() anteriormente?

Es posible hacer lo mismo con la sintaxis de spread:

				
					let arr = [1, 2, 3];

let arrCopy = [...arr]; // separa el array en una lista de parámetros
                        // luego pone el resultado en un nuevo array

// ¿los arrays tienen el mismo contenido?
console.log(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

// ¿los arrays son iguales?
console.log(arr === arrCopy); // false (no es la misma referencia)

// modificando nuestro array inicial no modifica la copia:
arr.push(4);
console.log(arr); // 1, 2, 3, 4
console.log(arrCopy); // 1, 2, 3

				
			

Nota que es posible hacer lo mismo para copiar un objeto:

				
					let obj = { a: 1, b: 2, c: 3 };

let objCopy = { ...obj }; // separa el objeto en una lista de parámetros
                          // luego devuelve el resultado en un nuevo objeto

// ¿tienen los objetos el mismo contenido?
console.log(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

// ¿son iguales los objetos?
console.log(obj === objCopy); // false (no es la misma referencia)

// modificando el objeto inicial no modifica la copia:
obj.d = 4;
console.log(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
console.log(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}

				
			

Esta manera de copiar un objeto es mucho más corta que let objCopy = Object.assign({}, obj); o para un array let arrCopy = Object.assign([], arr);, por lo que preferimos usarla siempre que podamos.

Resumen

Cuando veamos «…» en el código, se trata de parámetros rest o el operador spread.

Hay una manera fácil de distinguir entre ellos:

  • Cuando ... se encuentra al final de los parámetros de una función, son los “parámetros rest” y recogen el resto de la lista de argumentos en un array.
  • Cuando ... está en el llamado de una función o similar, se llama “operador spread” y expande un array en una lista.

Patrones de uso:

  • Los parámetros rest son usados para crear funciones que acepten cualquier número de argumentos.
  • El operador spread es usado para pasar un array a funciones que normalmente requieren una lista de muchos argumentos.

Ambos ayudan a ir entre una lista y un array de parámetros con facilidad.

Todos los argumentos de un llamado a una función están también disponibles en el “viejo” arguments: un objeto similar a un array iterable.

Related Post

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *