Cómo usar Map en Javascript (ES6)

Una de las funciones interesantes que podemos hacer uso con Javascript, para hacer nuestro código más elegante es Map. ¿Qué hace esta función? ¿Cuándo es aconsejable usarla?

Entendemos por la función Map, aquella función que «crea un nuevo array con los resultados de la llamada a la función indicada aplicados a cada uno de sus elementos», es decir, una función para modificar arrays y que nos devuelva el dicho array modificado.

 

Array Sencillos

Un ejemplo sencillo sería:

var numbers = [2, 4, 6, 8];
var doubles = numbers.map(function(x) {
   return x * 2;
});
// doubles es ahora [4, 8, 6, 16]
// numbers es todavía [2, 4, 6, 8]

Como podéis comprobar en la función, estamos aplicando la función map para que multiplique por dos cada elemento de la array, y luego nos lo devuelva.

Este ejemplo, lo podéis encontrar utilizando también los arrow function:

var numbers = [1, 5, 10, 15];
var doubles = numbers.map((x) => {
   return x * 2;
});

La diferencia reside en que podemos eliminar la palabra function, y nos queda más resumido. Pero además, si lo que incluimos dentro del map, solo ocupa una línea, lo podemos resumir un poco más:

var numbers = [1, 5, 10, 15];
var doubles = numbers.map((x) => x * 2);

Ahora ya no tenemos las llaves, ni el punto y coma para terminar la función, ni el return.  Esta forma nos permite tener el código más resumido. Pero hay que tener cuidado de no combinarlo con demasiadas funciones, para que no quede un código demasiado complejo.

 

Array de Objetos

Lo más habitual en los proyectos, es que acabemos trabajando con array de objetos, y el .map nos permitirá jugar y trabajar con diferentes combinaciones. Un ejemplo sencillo sería:

const pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];

const powerUp = pokemon.map((poke) => poke.ataque * 2);

// powerUp = [ 10, 12, 16 ]

Como podéis comprobar en la función, estamos multiplicando el ataque de nuestros pokemon por dos. La razón por la cual he podido crear como const la variable pokemon, es para que veáis que en ningún momento se modifica el valor.

 

Acceder al index

En algunas ocasiones, tendemos la necesidad en nuestra función que recorre el array, utilizar el index, es decir, saber en que posición esta la iteración. Esto es bastante habitual en el uso de templates de frameworks/librerías de angular, react o vue.

En el map, tendremos acceso a tres parámetros diferentes, que en la mayoría de los casos no los tendremos que utilizar. Pero aún así, los vamos a ver:

const pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];

const powerUp = pokemon.map((poke, posicion, arrayCompleto) => posicion * 2);

// powerUp = [ 0, 2, 4 ]

Como podéis ver, vamos a tener la posibilidad de utilizar 3 parámetros:

  • currentValueEl elemento actual del array que se está procesando. Esta sería la propiedad que hemos estado usando anteriormente
  • indexEl índice del elemento actual dentro del array: este es el ejemplo que estamos viendo
  • arrayEl array sobre el que se llama map: este sería el array completo

En el ejemplo, podemos ver que al multiplicar *2 la posición, nos da los valores 0, 2 y 4. Esto es así, porque al recorrer el array, comenzamos por la posición 0, luego la 1 y por último, la 2.

 

Optimizando código

Siempre que veamos un bucle for, foreach, for .. of y de este tipo, puede ser que podamos convertirlo en un .map para que mejore el rendimiento y el código se vea más sencillo.

const pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];

let powerUp = [];
for (let i=0; i<pokemon.length; i++) {
    powerUp.push(pokemon[i].ataque * 2);
}

const powerUpMap = pokemon.map((poke) => poke.ataque * 2);

/* 
powerUp = [ 10, 12, 16 ]
powerUpMap = [ 10, 12, 16 ]
*/

 

Malos hábitos

Tener cuidado de no mal usar esta función, recordando que el objetivo del .map es devolver un nuevo array. Si hacemos muchas más tareas dentro de las que debemos, tendremos un código complicado, difícil de leer y difícil de testear.

Modificar el objeto que recorremos

let pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];


pokemon.map((poke) => poke.ataque *= 2);

Podemos observar que nuestra función map no asigna a ninguna variable nueva ningún valor. Además, está modificando el array que está recorriendo. En este caso se le está dando un uso incorrecto a la función map, dado que es para generar un array nuevo con valores modificados. Para lo que estamos haciendo, deberíamos utilizar otra función diferente.

 

Multiple responsabilidades

Deberemos intentar desarrollar nuestras funciones para que tengan una sola responsabilidad. Esto se deberá aplicar también al map. En el caso de que aún así estemos realizando más modificaciones, nos estamos arriesgando a complicar el código, a complicar los test unitarios y a tener errores derivados y no controlados por este uso de esta función.

let pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];


const pokemonUp = pokemon.map((poke) => {
    poke.ataque *= 2;
    return poke.nombre;
});

Como vemos en el ejemplo, nuestra función map, además de crear un nuevo array con ciertos valores modificados, está modificando el array original. Con este tipo de implementaciones habrá que tener mucho cuidado, porque nos podrá inducir a errores colaterales, además de complicarnos nuestros test unitarios.

 

Abuso de Ternarias

El combinar condiciones ternarias con estos métodos funcionales puede provocar que nuestro código sea complicado de entender.

let pokemon = [
    { nombre: 'squirtle', ataque: 5},
    { nombre: 'bulbasaur', ataque: 6},
    { nombre: 'charmander', ataque: 8}
];


const pokemonUp = pokemon.map((poke) => poke.nombre === 'squirtle' ? poke.ataque * 2 : poke.ataque / 2 );

// [ 10, 3, 4 ]

En este caso, lo que estamos haciendo, es multiplicar por dos el ataque cuando el nombre sea squirtle y dividir entre dos, cuando no lo sea. Aunque parezca una función bastante sencilla, la combinación en una sola línea con el map con calculos complejos, suele conseguir que el código sea complicado de comprender. Más aún, cuando combinamos una ternaria dentro de otra.

Solo en los casos que el código sea comprensible, será aconsejable utilizar las ternarias.

 

Comparación de rendimiento

Si lo comparamos con los bucles habituales de JS de for, for .. of, foreach, while, etc… podemos encontrar que el método .map es más rápido que ellos en los bucles de tamaños pequeños y medianos, pero a medida que recorremos arrays de más de 100k, el performance se iguala e incluso a veces, es peor.

La gráfica muestra una comparativa de map y for, con diferentes versiones de node y con tamaños diferentes de array. El tamaño de las barras nos viene a indicar las operaciones por segundo y por item que realiza en el array. Como podéis apreciar, en los arrays de length 100, el .map es muy superior al bucle for. Pero en aquellos que son superiores a 10K, se iguala mucho el resultado, mientras que el bucle for, sigue en valores muy similares.

 

Bibliografía: