Javascript. Maps y Weak Maps
El objeto Map de Javascript fue añadido para resolver algunas de las limitaciones con las que contaba Javascript. ¡Descubre todas sus características!
Recientemente publiqué una receta “javascriptera” en la que hablaba de algunos de los usos que podemos dar al objeto Map y cuáles son sus ventajas con respecto a emplear objetos planos de Javascript.
Dado que el formato “receta” se queda corto he decidido escribir este artículo profundizando más en detalle tanto en el objeto Map de Javascript como en el objeto WeakMap, el cual tiene interesantes implicaciones a nivel de rendimiento.
¡Vamos allá!
El objeto Map
Antes de ver las diferencias de Map
con los objetos planos, veamos primero la forma de crear este tipo de objetos y emplearlos.
Lo primero de todo podemos crear un Map del siguiente modo:
const map = new Map();
Si queremos inicializarlo directamente desde su constructor podemos pasar un array de arrays “clave-valor”:
const initialValues = [["key1", "value1"], ["key2", "value2"]];
const map = new Map(initialValues);
Una vez que tenemos un objeto Map
podemos acceder a 3 métodos principales:
has(key)
para comprobar si existe una clave.get(key)
para recuperar el valor de una clave.set(key, value)
para añadir una nueva clave con su valor correspondiente o sobrescribir un valor ya existente.
Además también podemos obtener un iterador sobre las claves de un objeto Map
mediante el método keys
:
const map = new Map([['foo', 1], ['bar', 2]]);
console.log(map.keys()); // MapIterator {"foo", "bar"}
Que por supuesto podemos convertir a array de una forma muy sencilla:
[...map.keys()];
Otra cosa que es necesaria recordar de este objeto es que el orden dentro de un map es el de inserción. Es decir, cuando invoquemos el método keys
o iteremos sobre los valores de un objeto Map
los obtendremos en el orden en que los hayamos insertado.
Y hablando de iteración, los objetos Map
nos permiten iterar directamente sobre ellos bien empleando su método forEach
:
map.forEach((key, value) => { /* do something */ });
Bien empleando un bucle for...of
:
for ([key, value] of map2) {
console.log(`${key} ${value}`);
}
Diferencias con los objetos planos de Javascript
Ahora que ya conocemos el objeto Map
cabe preguntarse en qué momentos tiene sentido emplearlo en vez de los objetos planos que proporciona el lenguaje.
La primera y más es importante es la posibilidad de usar más tipos de valores para definir claves. En Javascript, cuando empleamos objetos las claves que identifican a sus propiedades tienen que ser bien strings
o Symbol
. En el momento en que tratemos de usar otro tipo de valores, Javascript tratará de convertirlos directamente a string:
const a = {};
const b = {[a]: 'foo'};
console.log(b); // {[object Object]: "foo"}
Sin embargo con un objeto Map
podemos emplear tanto objetos como funciones y booleanos como claves para los valores:
const a = {};
const b = true;
const fn = () => {};const map = new Map();
map.set(a, 'foo');
map.set(b, 'bar');
map.set(fn, 'zeta');console.log(map.get(a)); // 'foo'
console.log(map.get(b)); // 'bar'
console.log(map.get(fn)); // 'zeta'
Por otra parte, los maps permiten acceder a su tamaño mediante su propiedad size
algo que con los objetos no podemos hacer.
const map = new Map([['foo', 1], ['bar', 2]]);
console.log(map.size); // 2
Además como habéis visto ya, un objeto Map
es directamente iterable en un bucle for...of
mientras que los objetos planos en Javascript no lo son.
Finalmente, y en lo que se refiere a rendimiento, la documentación de MDN propone emplear objetos Map
en vez de objetos planos cuando necesitamos realizar numerosas inserciones y borrados de claves.
El objeto WeakMap
Ahora que ya conocemos el objeto Map
, creo que es muy interesante mencionar su versión “Weak”, es decir, un tipo de Map
que establece un enlace débil con las claves empleadas de modo que pueden ser recolectadas por el “garbage collector” cuando no existen más referencias a ellas.
♻️ ¿Garbage collector?
Javascript cuenta con un mecanismo por el cual aquellos objetos que hayamos declarado pero que tras un cierto tiempo ya no se usan en ninguna parte de nuestra aplicación (es decir, no existe ninguna referencia a ellos) , son eliminados de la memoria. Esto ayuda a controlar que no colapsemos la memoria del dispositivo que ejecuta el programa.
Características de los WeakMaps
Un objeto WeakMap
sólo puede tener objetos como claves (de modo que tenga sentido permitir que el “garbage collector” los pueda eliminar algo que no puede suceder con valores primitivos como “strings” o “numbers”).
Que tengamos que usar objetos como claves para un WeakMap tiene otra implicación: es necesario que todavía poseamos una referencia a ese objeto para poder acceder a su valor dentro del WeakMap:
const a = {};
const weakMap = new WeakMap();
weakMap.set(a, 'foo');// if we loose a, we loose the abject to 'foo'
Además, un WeakMap:
- No tiene acceso a la propiedad
size
a diferencia de lo que sucedía conMap
. - No son iterables ni podemos obtener una lista de sus claves.
Usos de los WeakMaps
Con las “limitaciones” mencionadas anteriormente, ¿cuándo tiene sentido usar un WeakMap
? Veamos algunos casos interesantes.
1️⃣ Puesto que un valor en un WeakMap
sólo existirá en tanto en cuanto el objeto que actúa como clave existe, podemos emplear un WeakMap
para almacenar características extra de un objeto sin vernos obligados a modificarlo:
const person = {name: 'Gerardo'};
const weakMap = new WeakMap();
weakMap.set(person, 'some interesting property of person';
2️⃣ Por supuesto los WeakMap
son unos objetos muy útiles a la hora de “cachear” información por la relación débil que establecen con los objetos. Por ejemplo, si estamos realizando operaciones pesadas sobre objetos del mismo “tipo”, podemos emplear un WeakMap
para almacenar los resultados obtenidos y tener la seguridad de que esos datos desaparecerán a la vez que lo haga el objeto:
function calculateShipping(item) {
/* some expensive operations */
}const itemInCart = {...};
const map = new WeakMap();
map.set(itemInCart, calculateShipping(itemInCart));
Conclusiones
Como veis, tanto los Map
como los WeakMap
nos pueden resultar muy útiles para determinados casos. Por eso siempre os animo a no quedaros en la superficie de un lenguaje (en este caso Javascript) sino que profundicéis en todas las características que ofrece ya que pueden resultar muy muy útiles para resolver problemas que nos costaría mucho tiempo resolver si no contáramos con estas herramientas.
Apóyame en Patreon
🧡🧡🧡 Gracias a: Ktron, Joseba, Alex y Jorge.
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇