En esta sesión vamos a ver cómo trabajar con arrays de forma eficiente en JavaScript. Hasta ahora hemos trabajado con arrays y conocemos algunos métodos del objeto array, como push
para meter nuevos elementos en el array o join
para unir todos los elementos de un array en una cadena. Y cuando queremos acceder todos los elementos de un array, usamos un bucle para recorrerlo. Pero en esta sesión vamos a aprender a realizar acciones con varios elementos de un array pero sin necesidad de bucles, usando los denominados métodos funcionales de array. Se llaman métodos funcionales porque están alineados con una forma de programar que da mucha importancia a las funciones... ¡nuestras amigas las funciones!
Usar los métodos funcionales de array nos sirve para poder operar con los valores contenidos en un array de una forma elegante a la vez que fácil de leer.
Los métodos funcionales de array pueden ser utilizados en cualquier aplicación que trabaje con arrays, y es muy usado en entornos concretos como React, una tecnología que veremos más adelante en el curso.
Con estos métodos funcionales podemos realizar las mismas acciones para las que necesitaríamos un bucle, por ejemplo:
buscar un elemento en un array
sumar los elementos de un array
aplicar una transformación a todos los elementos de un array
filtrar los elementos de un array, es decir, quedarnos sólo con los que cumplen un criterio
ordenar los elementos de un array según un criterio
Vamos a ver algunos de estos métodos y descubriremos su utilidad usando ejemplos de ejercicios que ya hemos hecho en el pasado pero ahora con métodos funcionales.
El método map
nos permite aplicar una función a todos los elementos de un array y devuelve otro array de la misma longitud con los resultados de aplicar esa función sobre cada elemento.
Vamos a ver cómo usarlo. En este ejemplo en codepen, partimos de un array con nombres names
y queremos obtener otro array con los nombres en mayúscula capitalNames
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const capitalNames = [];for (let i = 0; i < names.length; i++) {const capitalName = names[i].toUpperCase();capitalNames.push(capitalName);}console.log(capitalNames);
En el bucle, simplemente llamamos a la función toUpperCase
sobre cada elemento del array de forma que la cadena se convierte en mayúsculas. Después, sólo metemos el resultado en un nuevo array al que hemos llamado capitalNames
, usando push
.
Ahora vamos a ver cómo realizar esto mismo usando map
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const capitalNames = names.map(name => name.toUpperCase());console.log(capitalNames);
En este caso ejecutamos el método map
sobre el array de nombres names
. A map
le pasamos un único parámetro que es una función que se va a aplicar sobre cada elemento del array. Esta función (que hemos decidido hacer con una arrow function
) toma como parámetro el elemento del array, al que hemos llamado name
. Nosotros no ejecutamos esta función, sino que solo se la pasamos como parámetro a map
, justo de la misma forma que hacíamos con los callbacks, y será map
quien la ejecute pasándoles como argumento cada elemento del array. Dentro de la función tenemos el elemento del array (el nombre, por ejemplo, en primer lugar 'María') sobre el que ejecutamos directamente el método toUppercase
(pasar a mayúscula). Devolvemos (con un return
implícito) el resultado para que pase al array de resultados capitalNames
. En este caso nosotros no hemos tenido que crear el array capitalNames
a mano sino que map
lo crea directamente porque así es como funciona: devuelve un array del mismo tamaño que el original con el resultado de aplicar una función a cada elemento del array.
NOTA: es importante recordar que el array resultante de aplicar map va a ser siempre de la misma longitud que el array original.
Es importante recordar que el único argumento que recibe la función map
(al igual que resto de funciones que vamos a ver hoy) es una función. Y para que se entienda perfectamente vamos a rehacer el ejemplo anterior sustiyendo el código name => name.toUpperCase()
por la función getUperCaseName
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const getUperCaseName = name => name.toUpperCase();const capitalNames = names.map(getUperCaseName);console.log(capitalNames);
¿Qué argumento recibe map
?
Inflar las notas
¡Ya tenemos las notas del examen! Los profes, como somos así, las hemos metido en un array: const marks = [5, 4, 6, 7, 9];
. Casi todo el mundo lo ha hecho bastante bien pero... vamos a hacer un poco de trampa de la buena :) Vamos a modificar las notas de todas para añadirles 1 punto, ¿no? Pues usemos nuestro reciente amigo map
para crear un nuevo array inflatedMarks
con las notas modificadas. Finalmente, mostraremos en la consola las notas modificadas para ver que funciona correctamente. ¡Al lío!
__________
Saludar es de buena educación
Estamos creando una aplicación web, y lo primero que queremos hacer es saludar al usuario por su nombre, ¡como es debido! Tenemos un array con el listado de usuarios de nuestra aplicación const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];
y queremos conseguir otro array con los saludos, por ejemplo, 'Bienvenida Yolanda'. ¿Podríamos usar map
para que nos echase una mano?
__________
Gracias por confiar en nosotros
Seguimos desarrollando nuestra aplicación web que romperá el mercado. Pero antes, queremos agradecer a nuestros usuarios premium (de pago) su ayuda en el saludo de la aplicación. Por tanto, a los usuarios premium queremos saludarles así 'Bienvenida Yolanda. Gracias por confiar en nosotros.', y mantener el saludo simple 'Bienvenida Yolanda' para el resto de usuarios.
Vamos a partir de este array con el listado de usuarios que incluye tanto su nombre como si son usuarios premium o no.
Tenemos que crear un nuevo array con los saludos. ¿Podremos hacerlo con map
?
const users = [{ name: 'María', isPremium: false },{ name: 'Lucía', isPremium: true },{ name: 'Susana', isPremium: true },{ name: 'Rocío', isPremium: false },{ name: 'Inmaculada', isPremium: false }];
__________
El siguiente método funcional que vamos a ver es filter
. filter
nos ayuda a, como su propio nombre indica, filtrar un array y elegir algunos de sus elementos dado un criterio. La forma de uso es muy parecida a map
ya que toma como único argumento una función que se aplica sobre cada elemento del array. Si al ejecutar la función sobre un elemento esa función devuelve true
el elemento se mantiene en el array de resultados, pero si es false
, no se meterá. Por tanto, el array que crea filter
siempre va a tener una longitud igual o menor que el original: va a tener como máximo los elementos del original y como mínimo estará vacío.
Partimos de un ejemplo en el que, dado un listado de nombres queremos quedarnos solo con los que tienen más de 5 letras, es decir, 6 o más. Primero vamos a solucionarlo con un bucle:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const longNames = [];for (const name of names) {const nameLength = name.length; // ¡Sí, podemos usar .length con strings para saber su longitud!if (nameLength > 5) {longNames.push(name);}}console.log(longNames);
Como en el caso del map
recorremos el array usando un bucle y hemos creado un array longNames
para almacenar el resultado. Dentro del bucle accedemos a la longitud del nombre con la propiedad length
. Después lo comparamos con 5: si es mayor lo metemos en el array de resultados, pero si no lo es pues no lo metemos.
Ahora vamos a realizar este mismo ejemplo con filter
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const longNames = names.filter(name => name.length > 5);console.log(longNames);
En este caso hemos ejecutado el método filter
sobre el array names
y le pasamos como parámetro una función que es la que se ejecuta sobre cada elemento del array. Esta función (anónima) define un parámetro que hemos llamado name
que representa el elemento del array, por ejemplo, 'María'. Dentro de la función comparamos la longitud (length
) del nombre con 5, y devolvemos el resultado de esa comparación. Es decir, devolvemos true
(si la longitud del nombre es mayor que 5) o false
(si no lo es).
NOTA: El return siempre deberá devolver un booleano o una operación que devuelva un valor de este tipo, por ejemplo,
3 < 4
o'hola' === 'adios'
.
Solo los premium
Seguimos con nuestra app de moda y vamos a utilizar el listado de usuarios del ejercicio 3. Pero ahora queremos tener un listado de usuarios (en un array premiumUsers
) que solo tenga los usuarios premium. ¿Sabremos hacerlo con filter
?
__________
Los pares pueden entrar
Tenemos un listado de las contraseñas (PIN de 4 números) de los usuarios de nuestra web:
const pins = [2389, 2384, 2837, 5232, 8998];
De ese listado de contraseñas, queremos que solo puedan entrar los que han elegido una contraseña que es un número par para hacer A/B testing. ¿Nos ayudas a encontrar las contraseñas usando filter
?
PISTA: Recuerda que el resto de la división entera (módulo
%
) de número par es 0.
__________
Los usuarios que pueden entrar
Ya hemos conseguido las contraseñas pertenecientes a cada usuario. ¿Podrías darnos un array con los usuarios que pueden acceder a la aplicación, es decir, los que tienen como PIN un número par?
const users = [{ name: 'María', isPremium: false, pin: 2389 },{ name: 'Lucía', isPremium: true, pin: 2384 },{ name: 'Susana', isPremium: true, pin: 2837 },{ name: 'Rocío', isPremium: false, pin: 5232 },{ name: 'Inmaculada', isPremium: false, pin: 8998 }];
__________
El método reduce
es un método funcional complejo que nos permite realizar cálculos o acciones que requieran utilizar varios elementos de un array. A diferencia de map
o filter
el resultado de reduce
no es un array sino un valor del tipo que queramos. Se basa en aplicar una función a todos los elementos de un array (como las anteriores) y se va trabajando con resultados parciales hasta que se llega al resultado final. Se usa cuando queremos obtener un resultado que depende de varios de los elementos del array, por ejemplo, calcular la media de un listado de números.
Vamos a empezar con un ejemplo de la sesión sobre arrays que calcula la suma de un listado de números:
const scores = [4, 2, 7, 8, 6, 7, 9, 1, 2, 6, 7];let result = 0;for (let i = 0; i < scores.length; i++) {result += scores[i];}console.log(result);
En la variable result
, que comienza siendo 0, vamos acumulando la suma de todos los números del array accediendo a cada uno como scores[i]
dentro del bucle.
Vamos a ver cómo haríamos este mismo ejemplo con reduce
:
const scores = [4, 2, 7, 8, 6, 7, 9, 1, 2, 6, 7];const result = scores.reduce((acc, number) => acc + number, 0);console.log(result);
En este caso ejecutamos el método reduce
sobre el array scores
y le pasamos como parámetros 1) una función y 2) un valor inicial.
La función se ejecuta por cada elemento del array y toma como parámetros: a) un acumulador acc
, que acumula el resultado de un elemento al siguiente; y b) el elemento del array, por ejemplo, en la primera vuelta será el de índice 0 cuyo valor es 4.
El segundo parámetro, en este caso 0
, es el valor inicial del acumulador.
La función lo que hace es sumar al acumulador el valor del número actual y devuelve el resultado y ese mismo resultado se convierte en el acumulador del siguiente paso. Vamos a ver cómo funciona internamente:
Se ejecuta la función sobre el primer valor del array (4
) que tiene como argumentos acc
con valor 0 (valor inicial que hemos pasado al acumulador) y number
que es 4, y devuelve la suma 4 + 0
que es 4 y se convierte en el valor del acumulador.
Para el segundo valor, los argumentos son acc
que vale 4 y number
que es 2, y devuelve la suma que es 6 y será el valor del acumulador en el siguiente paso
La función toma como argumentos acc=6
y number=7
y devuelve 13
Y así sucesivamente hasta llegar al último elemento del array, que sumará al acumulado 7 y devolverá el resultado final, que es la suma de todos los números del array (59).
NOTA: el segundo parámetro de
reduce
(el valor del acumulador) es opcional y si no lo pasamos se toma como valor inicial el primer elemento del array. En nuestro ejemplo anterior sería válido no indicar segundo parámetro y comenzaríamos a aplicar la función a partir del segundo elemento (en el caso anterior el2
) que toma como acumulador el primero (en el caso anterior el4
).
const result = scores.reduce((acc, number) => acc + number);
Esta forma de trabajar es bastante compleja y requiere de mucha práctica, así que vamos a practicar realizando unos ejercicios.
La media de la carrera
Hemos organizado una carrera de escobas para que podáis exprimir a fondo vuestra flamante Nimbus 2000. Tenemos los tiempos en este array y nos gustaría conocer la media: ¿nos ayudas a calcularla usando reduce
?
const times = [56, 9, 45, 28, 35];
__________
El ganador de la carrera
Ya hemos conseguido los nombres de los competidores y nos gustaría que usases reduce
para averiguar quién ha ganado.
PISTA: en este caso el acumulador puede ser no sólo un número sino cualquier valor. Por ejemplo un objeto que sea nuestro candidato a ganador :)
const runners = [{ name: 'Gregory Goyle', time: 56 },{ name: 'Nymphadora Tonks', time: 9 },{ name: 'Luna Lovegood', time: 45 },{ name: 'Cedric Diggory', time: 28 },{ name: 'Cho Chang', time: 35 }];
__________
Dado que el resultado de aplicar map
y filter
es un array, querríamos poder encadenar su comportamiento. reduce
también podemos encadenarlo, pero será normalmente el último ya que su resultado no tiene por qué ser un array. Vamos a ver un ejemplo en codepen.
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const longNames = names.filter(name => name.length > 5).map(name => name.toUpperCase());console.log(longNames);
En este caso queremos filtrar los nombres largos pero además obtenerlos en mayúscula. Para eso vamos a, primero filtrar con filter
por longitud del nombre y luego convertirlos en mayúscula usando map
.
El ganador de los estudiantes
Como en el ejemplo anterior vamos a averiguar quién ha ganado usando reduce
, pero queremos saber el ganador de los estudiantes, por lo que tendremos que filtrar primero quiénes lo son.
const runners = [{ name: 'Gregory Goyle', time: 56, student: true },{ name: 'Nymphadora Tonks', time: 9, student: false },{ name: 'Luna Lovegood', time: 45, student: true },{ name: 'Cedric Diggory', time: 28, student: true },{ name: 'Cho Chang', time: 35, student: true }];
__________
find
nos permite buscar un elemento en un array dada una condición. A diferencia de filter
, no devuelve un array con los resultados, sino un único elemento: el primero que cumpla la condición que establecemos. Por otro lado, su compañero findIndex
hace lo mismo pero en vez de devolver el elemento, nos devuelve su índice (posición en el array) que es muy útil si queremos, por ejemplo, borrarlo usando splice
.
Partimos de un ejemplo en el que, dado un listado de nombres queremos quedarnos con el primero que tenga más de 5 letras, es decir, 6 o más. Primero vamos a solucionarlo con un bucle:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];let longName;for (const name of names) {const nameLength = name.length;if (nameLength > 5 && longName === undefined) {longName = name;}}console.log(longName);
Recorremos el array usando un bucle y hemos creado una variable longName
para almacenar el resultado. Dentro del bucle comparamos la longitud del nombre con 5 y además comprobamos que la variable longName
no esté aún definida para asegurarnos que es el primer nombre que cumple esta. Si se cumplen ambas condiciones metemos el nombre en la variable de resultados, y así el resto de nombre que sean más largos de 5 letras no se guardarán porque longName
ya está definido (no es undefined
).
Ahora vamos a realizar este mismo ejemplo con find
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const longName = names.find(name => name.length > 5);console.log(longName);
Igual que en el caso de map
y filter
, al método find
de array le pasamos como parámetro una función que es la que se ejecuta sobre cada elemento del array. En la función comparamos la longitud (length
) del nombre con 5, y devolvemos el resultado de esa comparación. El primer elemento cuyo resultado de la comparación de como resultado true
será devuelto por la función y lo recogemos en la variable longName
.
Ahora vamos a ver un ejemplo con findIndex
:
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];const index = names.findIndex(name => name.length > 5);console.log(index);
En este caso el resultado es 2 porque es el índice del array del primer nombre con más de 5 letras (Susana).
Encuentra el usuario
a) En nuestra aplicación de gestión de usuarios, nos ha llegado una incidencia asociada al PIN 5232. ¿Podrías encontrar el usuario que corresponde a ese PIN para poder contactarle? Usa el método find
para conseguirlo.
const users = [{ name: 'María', isPremium: false, pin: 2389 },{ name: 'Lucía', isPremium: true, pin: 2384 },{ name: 'Susana', isPremium: true, pin: 2837 },{ name: 'Rocío', isPremium: false, pin: 5232 },{ name: 'Inmaculada', isPremium: false, pin: 8998 }];
b) Resulta que el usuario se ha dado de baja por la incidencia :( ¿Podrías borrarlo del array de usuarios? Usa el método findIndex
para encontrar su posición y bórralo usando splice
.
__________
Para terminar, vamos a ver un último método que nos permite ordenar los elementos de un array. Es diferente de los anteriores en que, en lugar de devolver un nuevo array, modifica directamente el array original. Vamos a ver algunos ejemplos.
Para ordenar valores que son cadenas, no es necesario usar ninguna función de ordenación ya que por defecto sort
ordena los elementos de un array alfabéticamente.
const names = ['María', 'Lucía', 'Susana', 'Rocío', 'Inmaculada'];names.sort();console.log(names);
Si queremos indicar otro tipo de orden, tendremos que pasar al método sort
una función que sepa qué hacer para ordenar 2 elementos. Esta función toma 2 parámetros (a
y b
) que son 2 elementos cualquiera del array y tenemos que devolver:
un número negativo si queremos que a
se posicione antes que b
en el array
un número positivo si queremos que b
se posicione antes que a
en el array
cero si queremos se comporten como valores iguales y en la ordenación aparezcan juntos
Vamos a ver un ejemplo de la función de ordenación para ordenar números:
const times = [56, 9, 45, 28, 35];times.sort((a, b) => a - b);console.log(times);
De esta forma, si un número a
es mayor que otro b
el resultado es positivo y b
se posiciona antes en el resultado. Lo contrario ocurre cuando a
es menor que b
. Si son iguales, el resultado es 0 y se quedan como están.
Clasificación de la carrera
Volviendo a nuestra carrera de escobas, queremos tener el array del ejercicio 8 ordenado para poder tener una clasificación de la carrera: ¿nos ayudar a hacerlo usando sort
?
PISTA: la función que le pasamos a sort toma como parámetros 2 elementos del array, así que para acceder a una propiedad de un objeto en la función podemos hacerlo con el operador punto (
a.time
), como hemos hecho hasta ahora ;).
__________
Poniendo orden en nuestros usuarios
Vamos a volver al listado de usuarios del ejercicio 6, porque nos ha dado la manía de tenerlos ordenados. ¿Podrías ordenarlos por orden alfabético? ¿Y por su número de PIN?
__________
En algunas ocasiones necesitaremos acceder al listado de propiedades de un objeto, que a priori no sabemos cuáles son. Por ejemplo, nos puede llegar la información de un libro de una petición a un API y queremos pintar en pantalla todas las propiedades que comienzan por 'ds_'. Para poder hacer esto usamos el método Object.keys
que nos devuelve el listado de las propiedades de un objeto en un array.
const book = {title: 'Harry Potter and the Deathly Hallows',ds_title: 'Harry Potter 7',author: 'J. K. Rowling',ds_author: 'Rowling',...};const keys = Object.keys(book);console.log(keys); //['title', 'ds_title', 'author', 'ds_author', ...]