Operador de propagación en JavaScript

En este tutorial, exploraremos una de las potentes funciones de la especificación ES6 de JavaScript: el operador de propagación. Aunque la sintaxis es simple, algo...

Introducción

En este tutorial, exploraremos una de las potentes funciones de la especificación ES6 de JavaScript: el operador de propagación. Aunque la sintaxis es simple, a veces la implementación es confusa si no la entiende correctamente. En este tutorial, desmitificaremos esos tres puntos ... de JavaScript que hace cosas asombrosas con iterables.

Usos del operador de propagación

Hay diferentes usos del operador de propagación y cada objetivo de uso para resolver una declaración de problema diferente.

Matrices en expansión {#matrices en expansión}

Podemos usar el operador de propagación en iterables como una cadena o una matriz y colocará el contenido del iterable en elementos individuales.

Para un ejemplo:

1
2
3
let greet = ['Hello', 'World'];
console.log(greet); // Without spread operator
console.log(...greet); // Using spread operator

Si ejecutamos este código veremos lo siguiente:

1
2
['Hello', 'World']
Hello World

Debes haber notado que en el segundo caso (con el operador de propagación), el contenido de la lista saludo se expandió y se eliminó de la matriz.

A veces, podemos sentir la necesidad de convertir un String en una lista de caracteres. Podemos usar el operador de propagación para este caso de uso:

1
2
3
let greetings = "hello";
let chars = [...greetings];
console.log(chars);

Si ejecutamos este código, seremos recibidos con:

1
[ 'h', 'e', 'l', 'l', 'o' ]

Es posible que estos ejemplos no le convenzan de la utilidad que ofrece este operador. En ese nombre, tomemos algunos problemas del mundo real que se pueden resolver con los operadores de propagación.

Combinación de matrices

Aprovechemos el hecho de que ahora podemos expandir una matriz usando el operador de expansión. Digamos que tenemos listas de suscriptores de dos fuentes diferentes y queremos combinar ambas fuentes y hacer una sola lista de suscriptores:

1
2
3
4
let blog1Subscribers = ['[correo electrónico protegido]', '[correo electrónico protegido]'];
let blog2Subscribers = ['[correo electrónico protegido]', '[correo electrónico protegido]', '[correo electrónico protegido]'];
let subscribers = [...blog1Subscribers, ...blog2Subscribers];
console.log(subscribers);

Si ejecutamos el código anterior, obtendremos una lista única de iterables. Esto fue posible porque tanto ...blog1Subscribers como ...blog2Subscribers se distribuyeron y [] actuó como "receptor", que combinó efectivamente los elementos distribuidos en una sola lista de elementos.

Nota: El operador de propagación necesita que el receptor coloque el valor expandido. Si omite el receptor, arrojará un error.

También podemos usar el operador de propagación dentro del método Array.push() para insertar el contenido de una matriz en otra:

1
2
3
4
let arr1 = ['John', 'Sofia', 'Bob'];
let arr2 = ['Julia', 'Sean', 'Anthony'];
arr1.push(...arr2);
console.log(arr1);

Si ejecutamos este código, veremos el siguiente resultado:

1
[ 'John', 'Sofia', 'Bob', 'Julia', 'Sean', 'Anthony' ]

Copiar matrices y objetos {#copiar matrices y objetos}

En JavaScript, cada entidad no primitiva es un Objeto, lo que significa que las matrices también son objetos. Es posible que sepa que los objetos se copian como un tipo de referencia:

1
2
3
4
5
let arr1 = ['John', 'Sofia', 'Bob'];
let arr2 = arr1;
console.log(arr2);
arr1.push('Sally'); // Change arr1
console.log(arr2);
1
2
[ 'John', 'Sofia', 'Bob' ]
[ 'John', 'Sofia', 'Bob', 'Sally' ]

Como era de esperar, los valores de los elementos de la matriz no se copiaron, solo la referencia a ellos. Podemos resolver este problema fácilmente con el operador de propagación:

1
2
3
4
5
let arr1 = ['John', 'Sofia', 'Bob'];
let arr2 = [...arr1];
console.log(arr2);
arr1.push('Sally'); // Change arr1
console.log(arr2);

Ejecutar este código produce lo siguiente:

1
2
[ 'John', 'Sofia', 'Bob' ]
[ 'John', 'Sofia', 'Bob' ]

Como podemos ver, arr2 no recibió una referencia como antes, sino que se completó con los valores de arr1 como un objeto completamente nuevo. Entonces, incluso cuando arr1 cambia, arr2 sigue siendo el mismo.

También podemos usar el operador de propagación para crear una copia de una matriz y agregarle nuevos elementos al mismo tiempo:

1
2
3
let arr1 = ['John', 'Sofia', 'Bob'];
let arr2 = [...arr1, 'Anthony', 'Sean'];
console.log(arr2);
1
['John', 'Sofia', 'Bob', 'Anthony', 'Sean']

Nota: El operador de propagación funciona con todos los iterables, incluidos los objetos.

Anteriormente, esto habría requerido una línea adicional de código para agregar los nuevos elementos a la nueva matriz.

De manera similar, podemos copiar objetos usando el operador de propagación:

1
2
3
let o1 = { a: 1, b: 2 };
let o2 = { c: 3, d: 4, ...o1 };
console.log(o2);
1
{ c: 3, d: 4, a: 1, b: 2 }

Como podemos ver, copiamos con éxito el objeto o1 en o2.

Esta característica tiene muchos casos de uso del mundo real. Por ejemplo, digamos que almacenamos información de registro de usuario en un objeto. Podemos hacer una copia superficial de ese objeto y agregar más información al objeto copiado:

1
2
3
let user = { name: 'John', email: '[correo electrónico protegido]' };
let _user = { ...user, ...getSession(user) };
console.log(_user);
1
{ name: 'John', email: '[correo electrónico protegido]', 'token': 'abc123', 'expiresAt': 1565630480671 }

Es posible que también necesitemos combinar la información de facturación y envío en una sola:

1
2
3
4
const billing = { billingContact: '0987654321', billingAddress: 'street no 123, xyz city' };
const shipping = { shippingContact: '123456789', shippingAddress: 'street no 999, abc city' };
const custInfo = { ...billing, ...shipping };
console.log(custInfo);

Si ejecutamos este código, deberíamos ser recibidos con:

1
2
3
4
5
6
{
  billingContact: '0987654321',
  billingAddress: 'street no 123, xyz city',
  shippingContact: '123456789',
  shippingAddress: 'street no 999, abc city'
}

Aquí se podría plantear una pregunta. ¿Qué pasa si ambos objetos tienen algunas de las mismas propiedades?

En caso de colisión de propiedades, gana la propiedad del último objeto. Veamos esto en un ejemplo:

1
2
3
const o1 = { a: 1, b: 2 };
const o2 = { b: 3, c: 4, ...o1};
console.log(o2);

Si ejecuta este código, debería ver lo siguiente:

1
{ b: 2, c: 4, a: 1 }

Como podemos ver las propiedades del segundo objeto o2 gana. Sin embargo, si ponemos el operador de propagación primero:

1
2
3
const o1 = { a: 1, b: 2 };
const o2 = { ...o1, b: 3, c: 4};
console.log(o2);
1
{ a: 1, b: 3, c: 4 }

Podemos ver que gana la propiedad de o1, lo cual tiene sentido ya que o2 es el último objeto.

Un caso de uso de esta función podría ser realizar asignaciones predeterminadas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const userProvided = {
    name: 'Bil Smith',
    email: '[correo electrónico protegido]',
};
const defaultValues = {
    name: 'Unknown',
    address: 'Alien',
    phone: null,
    email: null
};
const userInfo = { ...defaultValues, ...userProvided };

Alternativa a funciones de llamada con apply()

Digamos que una función toma un argumento: una lista de calificaciones de los 5 mejores estudiantes de una clase. También tenemos una lista procedente de una fuente externa. Seguramente, podemos evitar pasar elementos individuales y en su lugar pasar la lista completa usando el método apply():

1
2
3
4
5
6
myFun(m1, m2, m3, m4, m5) {
    // Do something
}

let marks = [10, 23, 83, -1, 92];
myFun.apply(undefined, arr);

Podemos deshacernos del confuso argumento indefinido y hacer que el código sea más limpio llamando a la función directamente con el operador de propagación:

1
2
3
4
5
6
myFun(m1, m2, m3, m4, m5) {
    // Do something
}

let marks = [10, 23, 83, -1, 92];
myFun(...marks);

Usar con funciones matemáticas

JavaScript tiene un objeto Math que contiene varios métodos para operar con un conjunto de datos, es decir, una lista de datos.

Digamos que queremos obtener el valor máximo de los tres primeros números de una lista:

1
2
let mylist = [10, 23, 83, -1, 92, -33, 76, 29, 76, 100, 644, -633];
Math.max(mylist[0], mylist[1], mylist[2]);

¿Qué pasa si queremos obtener el máximo de todos los números en una lista? ¿Qué sucede si la lista tiene n número de elementos? Seguramente no querremos mylist[0], mylist[1]... mylist[1000].

El operador de propagación proporciona una solución más limpia:

1
2
let mylist = [10, 23, 83, -1, 92, -33, 76, 29, 76, 100, 644, -633];
Math.max(...mylist);

Nota: Dado que el operador de distribución funciona tanto con Arreglos como con Objetos, a veces puede verse tentado a mezclarlos y combinarlos. ¡No hagas eso! Por ejemplo, la siguiente acción dará como resultado un error:

1
2
let user = {name:'John', age:28, email:'[correo electrónico protegido]'};
let items = [...user];
1
TypeError: user is not iterable

Operador de propagación frente a parámetro de descanso

Tanto el Spread Operator como el Rest Parameter comparten la misma sintaxis, es decir, los tres puntos mágicos .... Pero se comportan exactamente opuestos entre sí. Como principiante, a veces esto puede resultar confuso. El resultado final para comprender el comportamiento es comprender el contexto en el que se utiliza.

Como aprendimos, el operador de propagación expande el contenido de un iterable. Por el contrario, el operador rest recopila todos los elementos restantes en una matriz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function doSum(...items) {
    let sum = 0;
    for (let item of items){
        sum += item;
    }
    return sum;
}

doSum(1);
doSum(1,2);
doSum(1, 2, 3, 4);

Si ejecutamos el código anterior, seremos recibidos con lo siguiente:

1
2
3
4
1
3
6
10

Como podemos ver, cada vez que los elementos restantes fueron recopilados por el Parámetro de descanso.

También podemos proporcionar distintas variables para algunos de los elementos y hacer que el resto de parámetros recopile el resto de los elementos. La única condición es que el parámetro resto sea siempre el último parámetro de la función:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function doSum(times, ...items) {
    let sum = 0;
    for (let item of items){
        sum += item*times;
    }
    return sum;
}

doSum(1, 1);
doSum(2, 1, 2);
doSum(3, 1, 2, 3);

Si ejecutamos el código anterior, veremos lo siguiente:

1
2
3
1
6
18

Conclusión

Como podemos ver, el operador de propagación ... es una característica realmente poderosa de la especificación ES6 de JavaScript. Podemos resolver muchos de los problemas del mundo real fácilmente usando este operador. Como aprendimos de los diversos ejemplos discutidos en este artículo, nos permite escribir menos código y hacer más.

En este artículo, cubrimos los usos comunes del Operador de propagación. También discutimos el Parámetro de descanso de aspecto similar, pero diferente. Tenga en cuenta que podría haber docenas de otros casos de uso según el problema. ún el problema.