Saltar al contenido

Map y WeakMap en JavaScript

· 5 minutos de lectura

Mapa viejo.

Usualmente cuando pienso en almacenar información en JavaScript pienso en hacerlo en objetos o en arrays. Actualmente existen otras estructuras de datos que nos permiten almacenar información en JavaScript, una de ellas es el Map y el WeakMap, que ponen en relación una clave con un valor, pero con diferencias importantes.

Map

Los objetos Map1 son como un tipo de diccionarios en donde nosotros asociamos un key o una llave con un valor. Los objetos Map se construyen utilizando el keyword new y llamando a la clase Map. Una ventaja es que con los Map podemos utilizar cualquier tipo de dato como key.

javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
 
console.log(map.get("key"));
value
console.log(map.get(1));
uno
console.log(map.get(true));
verdadero
javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
 
console.log(map.get("key"));
value
console.log(map.get(1));
uno
console.log(map.get(true));
verdadero

Métodos

Los objetos Map tienen una serie de métodos que nos permiten interactuar con el mismo. Aquí tenemos una lista de los métodos que podemos utilizar con los objetos Map.

Set

Para asignar un key a un valor utilizamos el método set.

javascript
const map = new Map();
 
map.set("key", "value");
map.set(1, "uno");
map.set(true, "verdadero");
const simpleObject = {};
map.set(simpleObject, "objeto");
 
for ([key, value] of map) {
console.log(key, value);
}
javascript
const map = new Map();
 
map.set("key", "value");
map.set(1, "uno");
map.set(true, "verdadero");
const simpleObject = {};
map.set(simpleObject, "objeto");
 
for ([key, value] of map) {
console.log(key, value);
}

Clear

Elimina todos los valores asociados al objeto Map.

javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
console.log(map.get("key"));
value
 
map.clear();
console.log(map.get("key"));
undefined
javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
console.log(map.get("key"));
value
 
map.clear();
console.log(map.get("key"));
undefined

Delete

Elimina un valor asociado al objeto Map.

javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
console.log(map.get("key"));
value
console.log(map.get(1));
uno
 
map.delete("key");
console.log(map.get("key"));
undefined
console.log(map.get(1));
uno
javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
console.log(map.get("key"));
value
console.log(map.get(1));
uno
 
map.delete("key");
console.log(map.get("key"));
undefined
console.log(map.get(1));
uno

Has

Devuelve true si el objeto map tiene un valor asociado al key que se le pasa como parámetro. En caso contrario devuelve false.

javascript
const map = new Map();
map.set(window, "hey");
 
map.has(window); // true
map.has("no"); // false
javascript
const map = new Map();
map.set(window, "hey");
 
map.has(window); // true
map.has("no"); // false

forEach

Permite iterar sobre los valores asociados al objeto map. El método forEach recibe una función que recibe dos parámetros: el key y el value. El método forEach no devuelve ningún valor.

javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
map.forEach((value, key) => {
console.log(key, value);
});
javascript
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
map.forEach((value, key) => {
console.log(key, value);
});

WeakMap

El objeto WeakMap2 es similar al Map, que solo aceptan objetos como key y que pueden ser recolectados por el garbage collector.

Garbage Collector

El garbage collector es un proceso que se ejecuta en el navegador que elimina los objetos que ya no están referenciados de la memoria, esto ayuda a no consumir recursos innecesarios y que no colapse la memoria.

Características de WeakMap

Son similares a los objetos Map pero con algunas diferencias esenciales:

  • Solo se pueden asociar objetos como key.
ts
const weakMap = new WeakMap([
No overload matches this call. Overload 1 of 2, '(iterable: Iterable<readonly [object, string]>): WeakMap<object, string>', gave the following error. Argument of type '([string, string] | [number, string] | [boolean, string])[]' is not assignable to parameter of type 'Iterable<readonly [object, string]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<[string, string] | [number, string] | [boolean, string], any>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorYieldResult<readonly [object, string]>'. Type '[string, string] | [number, string] | [boolean, string]' is not assignable to type 'readonly [object, string]'. Type '[string, string]' is not assignable to type 'readonly [object, string]'. Type at position 0 in source is not compatible with type at position 0 in target. Type 'string' is not assignable to type 'object'.2769No overload matches this call. Overload 1 of 2, '(iterable: Iterable<readonly [object, string]>): WeakMap<object, string>', gave the following error. Argument of type '([string, string] | [number, string] | [boolean, string])[]' is not assignable to parameter of type 'Iterable<readonly [object, string]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<[string, string] | [number, string] | [boolean, string], any>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorYieldResult<readonly [object, string]>'. Type '[string, string] | [number, string] | [boolean, string]' is not assignable to type 'readonly [object, string]'. Type '[string, string]' is not assignable to type 'readonly [object, string]'. Type at position 0 in source is not compatible with type at position 0 in target. Type 'string' is not assignable to type 'object'.
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
ts
const weakMap = new WeakMap([
No overload matches this call. Overload 1 of 2, '(iterable: Iterable<readonly [object, string]>): WeakMap<object, string>', gave the following error. Argument of type '([string, string] | [number, string] | [boolean, string])[]' is not assignable to parameter of type 'Iterable<readonly [object, string]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<[string, string] | [number, string] | [boolean, string], any>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorYieldResult<readonly [object, string]>'. Type '[string, string] | [number, string] | [boolean, string]' is not assignable to type 'readonly [object, string]'. Type '[string, string]' is not assignable to type 'readonly [object, string]'. Type at position 0 in source is not compatible with type at position 0 in target. Type 'string' is not assignable to type 'object'.2769No overload matches this call. Overload 1 of 2, '(iterable: Iterable<readonly [object, string]>): WeakMap<object, string>', gave the following error. Argument of type '([string, string] | [number, string] | [boolean, string])[]' is not assignable to parameter of type 'Iterable<readonly [object, string]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<[string, string] | [number, string] | [boolean, string], any>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorResult<readonly [object, string], any>'. Type 'IteratorYieldResult<[string, string] | [number, string] | [boolean, string]>' is not assignable to type 'IteratorYieldResult<readonly [object, string]>'. Type '[string, string] | [number, string] | [boolean, string]' is not assignable to type 'readonly [object, string]'. Type '[string, string]' is not assignable to type 'readonly [object, string]'. Type at position 0 in source is not compatible with type at position 0 in target. Type 'string' is not assignable to type 'object'.
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
Uncaught TypeError: Invalid value used as weak map key
      at WeakMap.set (<anonymous>)
      at new WeakMap (<anonymous>)
      at <anonymous>:1:17
  
  • No tiene el método keys() por lo que no podemos acceder a values(), entries(), clear() o la propiedad size WeakMap y tampoco es iterable.
  • Los objetos asociados a un objeto WeakMap será recolectado por el garbage collector cuando ya no existan más referencias a ellos.

Ya que la consola de chrome es lazy, es probable que podamos ver actuar al garbage collector con el siguiente ejemplo al ver lo que pasa al convertir el objecto a null.

ts
let obj = {};
let map = new Map([[obj, "value"]]);
console.log(map.get(obj));
obj = null;
console.log(map);
 
let obj2 = {};
let weakMap = new WeakMap([[obj2, "value2"]]);
console.log(weakMap.get(obj2));
obj2 = null;
console.log(weakMap);
ts
let obj = {};
let map = new Map([[obj, "value"]]);
console.log(map.get(obj));
obj = null;
console.log(map);
 
let obj2 = {};
let weakMap = new WeakMap([[obj2, "value2"]]);
console.log(weakMap.get(obj2));
obj2 = null;
console.log(weakMap);
Garbage collector en weakMap

Al convertir el objeto a null, weakMap perdio la única propiedad que tenía con referencia a ese objeto

Diferencias entre Map y Object

  • Los objetos solo permiten asociar como key los strings o symbol. Si pasamos otro valor, JavaScript hará una coerción de datos para convertir el valor en un string.
  • Podemos acceder al tamaño de un Map con el método size, algo que con los objetos no podemos hacer.
  • La forma en la que podemos iterarlos es diferente.
example.ts
ts
// Map
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
for (let [key, value] of map) {
console.log(key, value);
}
 
// Objetos
const obj: Record<string, string> = { key: "value", key2: "value2" };
const keys = Object.keys(obj);
keys.forEach((key) => {
console.log(key, obj[key]);
});
 
// o bien
for (const key in obj) {
console.log(key, obj[key]);
}

Typescript se quejaría que le pasé 1 y true como key en lugar de string

example.ts
ts
// Map
const map = new Map([
["key", "value"],
[1, "uno"],
[true, "verdadero"],
]);
for (let [key, value] of map) {
console.log(key, value);
}
 
// Objetos
const obj: Record<string, string> = { key: "value", key2: "value2" };
const keys = Object.keys(obj);
keys.forEach((key) => {
console.log(key, obj[key]);
});
 
// o bien
for (const key in obj) {
console.log(key, obj[key]);
}

Typescript se quejaría que le pasé 1 y true como key en lugar de string

  • La forma de eliminar un valor de un Map es mediante el método delete y con los objetos mediante el operador delete.
ts
// Map
const map = new Map([
["key", "value"],
["1", "uno"],
["true", "verdadero"],
]);
console.log(map.size); // 3
map.delete("key");
console.log(map.size); // 2
 
// Object
const obj: Record<string, string> = {
key: "value",
1: "uno",
true: "verdadero",
};
console.log(Object.keys(obj).length); // 3
delete obj.key;
console.log(Object.keys(obj).length); // 2
ts
// Map
const map = new Map([
["key", "value"],
["1", "uno"],
["true", "verdadero"],
]);
console.log(map.size); // 3
map.delete("key");
console.log(map.size); // 2
 
// Object
const obj: Record<string, string> = {
key: "value",
1: "uno",
true: "verdadero",
};
console.log(Object.keys(obj).length); // 3
delete obj.key;
console.log(Object.keys(obj).length); // 2

Precaución: El operador delete en varios escenarios puede cambiar la velocidad arbitrariamente, por lo que no es muy recomendable de usar cuando se requiere un buen rendimiento de la aplicación.

Conclusión

Las estructuras de datos Map y WeakMap son otra alternativa para almacenar datos en forma de clave-valor, que pueden servir para casos especiales, según las necesidades de nuestra aplicación. Existen también estructuras de datos Set y WeakSet que son similares a estas, por lo que le puedes echar un vistazo.

Referencias

  1. MDN Web Docs & MDN contributors Map

  2. MDN Web Docs & MDN contributors WeakMap


Recibirás actualizaciones del blog con temas de programación

Descubre más sitios indie