"El buen diseño es obvio. El gran diseño es transparente"
- Joe Sparano

Iterar con map, filter y reduce

5 minutos de lectura
Fecha: 31/10/2018

Hoy vamos a hablar sobre como Iterar con map, filter y reduce (hace tiempo escribí un artículo sobre como Iterar con jQuery each, la función de iteración de la famosa librería de Javascript).

Si ya estas usando jQuery o Lodash para iterar sobre arrays te preguntarás que diferencia hay entre lo que haces ahora y usar estas funciones, ambas tienen ventajas e inconvenientes.

En el caso de usar librerías te ahorras tener que mirar si tu navegador soporta esos métodos, también podría tener particularidades que ya conozcamos y sean útiles, como que el each de jQuery también te permite iterar sobre los atributos de un objeto. A veces usamos librerías simplemente porque estamos acostumbrados a ellas y conocemos bien su sintaxis, aunque esto no es una ventaja de por si.

Por contra usar librerías te obliga a tener que cargarlas, aumentamos el peso de la web y nos atamos nuestro código a ellas. Obviamente no es malo usar alguna librería como estas que mencionamos pero siempre está bien saber como se usan los métodos nativos, que implementados o no por los navegadores, ya forman parte del estándar.

Para que os animéis a usarlo, mencionaros que realmente son métodos que llevan ya bastante tiempo entre nosotros (de hecho su estándar es ES5), y estan soportados por todos los navegadores modernos, os dejo la imagen de caniuse por si alguien no se fía (vale para los 3 métodos).

Descripción de la imagen

El método map

El método map nos permite iterar sobre un array y realizar transformaciones sobre él. Llegados a este punto la pregunta que igual te surge es para que quiero un método que haga esto si yo ya tengo el bucle for, o incluso el método forEach que también son nativos y ya me sirven.

Antes de nada, comentar que obviamente podemos realizar una iteración usando cualquiera de estos tres métodos, y que también podemos hacer transformaciones sobre los elementos del array. El punto es que no funcionan igual y según los casos puede ser más eficiente usar una u otro.

Esto lo podemos ver más claro usando un ejemplo. Pongamos que tenemos un array de objetos con modelos de coches, y queremos ir iterando sobre este array y guardar en otro array un texto que contenga la marca y el modelo…

Este es el array.

var cars = [{
    'brand': 'Ford',
    'model' : 'Focus'
  }, {
    'brand': 'Volkswagen',
    'model' : 'Golf'
  }, {
    'brand': 'Honda',
    'model' : 'Civic'
  }, {
    'brand': 'Seat',
    'model' : 'León'
}];

Con el bucle for de toda la vida haríamos algo así.

var cars_names = [];
for (var i = 0, max = cars.length; i < max; i += 1) {
    cars_names.push(cars[i].brand + '' + cars[i].model);
}

Esto funciona y hace lo que dijimos arriba, veamos ahora como sería usando un forEach.

var cars_names = [];
cars.forEach(function(car) {
    cars_names.push(car.brand + ' ' + car.model);
});

Hacer esto es exactamente lo mismo que arriba, pero tiene ventajas como que es mucho más legible y fácil de mantener porque nos evita tener que mantener el control de los indices.

Y ahora veamos como hacemos con el método map.

var cars_names = cars.map(function(car) {
    return car.brand + ' ' + car.model; 
});

La principal diferencia entre map y forEach que además es la responsable de todas sus ventajas es que mientras que forEach devuelve undefined teniendo que usar su función de callback para hacer la lógica, map retorna ya el producto final, o dicho de otra manera, devuelve el array con las modificaciones que le hagamos en la función de callback.

Gracias a esto con map podemos mapear directamente la variable cars_names contra el método map, y también nos ahorramos tener que usar el método push para irla rellenando en cada iteracion, algo muy útil.

Es importante recordar que esa función de callback de map tenga un return como vemos arriba, o sino nos va a devolver un array con todos sus elementos en undefined.

Otra ventaja que tiene map frente a forEach, que al devolver el array modificado podríamos encadenarla con otras funciones.

Antes de continuar, esa diferencia que comentábamos de que devuelve algo apoyándose en la función de callback, y que por tanto se puede asignar directamente y es encadenable, aplica también a los métodos filter y reduce, ya no lo voy a comentar cuando hable de ellos pero tenedlo en cuenta.

La salida que producen todos estos casos equivale a:

var cars_names = ['Ford Focus', 'Volkswagen Golf', 'Honda Civic', 'Seat León'];

El método filter

Mientras que con map tenemos un array modificado con filter tenemos un array filtrado, es decir, en vez de devolver un array con el mismo número de elementos pero modificados, devolvemos el array pero sin algunos elementos y sin modificarlos.

Pensemos en que tenemos el mismo array que antes pero que queremos filtrar la marca Seat.

var cars_names = cars.filter(function(car) {
    return car.brand !== 'Seat'; 
});

En este caso la función de callback también necesita tener un return, pero no se usa para lo mismo, en el map el return modificaba el elemento sobre el que iteraba, en filter el return es un booleano que indica si el elemento es filtrado o no.

En nuestro caso hemos puesto como expresión car.brand !== 'Seat', es decir que cuando llegue una marca que no sea Seat será evaluado a true y ese elemento será devuelto, pero cuando sea Seat evalúa a false y es filtrado.

La salida que produce equivale a:

var cars = [{
    'brand': 'Ford',
    'model' : 'Focus'
  }, {
    'brand': 'Volkswagen',
    'model' : 'Golf'
  }, {
    'brand': 'Honda',
    'model' : 'Civic'
}];

El método reduce

El último método es reduce y es el más difícil de entender de las 3.

Mientras que los otros métodos devolvían un array, el método reduce devuelve un solo valor y no una colección, de ahí su nombre porque reduce un array a un valor.

Tiene dos argumentos, la función de callback como en los otros casos y un valor inicial que se usar para arrancar en la primera llamada.

La función de callback también tiene distintos argumentos ya no es el elemento del array (y opcionalmente el indice y el array), sino que son:

  1. Valor anterior
  2. Valor actual
  3. Índice actual (opcional)
  4. Array sobre el que usaste reduce (opcional)

El ejemplo más sencillo que podemos hacer con este método es el de sumar todos los elementos de un array de números, pensemos en que tenemos esto:

var numbers = [5, 7, 20, 1];

Y ahora uso reduce para reducirlos a un solo valor que sea la suma de todos…

var total = numbers.reduce(function(valorAnterior, valorActual){
  return valorAnterior + valorActual;
}, 0);

La variable total ahora vale 44


Aunque en todos los ejemplos que he usado aquí empleo ES5 no quiere decir que no puedas usar estos métodos usando una sintaxis más moderna como el ES6, por ejemplo en la función map podría haber usado la interpolación de cadenas para concatenar las variables, y las funciones de callback podrían haber sido arrow functions, pero esta entrada era solo para hablar de estos métodos y no quise hacerlo así.