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 Map
1 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.
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]); console .log (map .get ("key"));console .log (map .get (1));console .log (map .get (true));
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]); console .log (map .get ("key"));console .log (map .get (1));console .log (map .get (true));
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
.
javascriptconst 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 );}
javascriptconst 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
.
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);console .log (map .get ("key")); map .clear ();console .log (map .get ("key"));
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);console .log (map .get ("key")); map .clear ();console .log (map .get ("key"));
Delete
Elimina un valor asociado al objeto Map
.
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);console .log (map .get ("key"));console .log (map .get (1)); map .delete ("key");console .log (map .get ("key"));console .log (map .get (1));
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);console .log (map .get ("key"));console .log (map .get (1)); map .delete ("key");console .log (map .get ("key"));console .log (map .get (1));
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
.
javascriptconst map = new Map ();map .set (window , "hey"); map .has (window ); // truemap .has ("no"); // false
javascriptconst map = new Map ();map .set (window , "hey"); map .has (window ); // truemap .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.
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);map .forEach ((value , key ) => { console .log (key , value );});
javascriptconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);map .forEach ((value , key ) => { console .log (key , value );});
WeakMap
El objeto WeakMap
2 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.
tsconst 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"],]);
tsconst 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 avalues()
,entries()
,clear()
o la propiedadsize
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
.
tslet 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 );
tslet 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 );
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.tsts// Mapconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);for (let [key , value ] of map ) { console .log (key , value );} // Objetosconst obj : Record <string, string> = { key : "value", key2 : "value2" };const keys = Object .keys (obj );keys .forEach ((key ) => { console .log (key , obj [key ]);}); // o bienfor (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.tsts// Mapconst map = new Map ([ ["key", "value"], [1, "uno"], [true, "verdadero"],]);for (let [key , value ] of map ) { console .log (key , value );} // Objetosconst obj : Record <string, string> = { key : "value", key2 : "value2" };const keys = Object .keys (obj );keys .forEach ((key ) => { console .log (key , obj [key ]);}); // o bienfor (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// Mapconst map = new Map ([ ["key", "value"], ["1", "uno"], ["true", "verdadero"],]);console .log (map .size ); // 3map .delete ("key");console .log (map .size ); // 2 // Objectconst obj : Record <string, string> = { key : "value", 1: "uno", true : "verdadero",};console .log (Object .keys (obj ).length ); // 3delete obj .key ;console .log (Object .keys (obj ).length ); // 2
ts// Mapconst map = new Map ([ ["key", "value"], ["1", "uno"], ["true", "verdadero"],]);console .log (map .size ); // 3map .delete ("key");console .log (map .size ); // 2 // Objectconst obj : Record <string, string> = { key : "value", 1: "uno", true : "verdadero",};console .log (Object .keys (obj ).length ); // 3delete 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.
Houssein Djirdeh@hdjirdeh ·Today I learned about the delete operator in JavaScript. How have I never heard about this before?
@hdjirdeh Since we can’t explicitly call JavaScript’s garbage collector, it usually isn’t a great idea to mark objects as deleted, using the delete keyword, as only objects with no existing references to them will be cleaned. It might be more performant to use the rest operator
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.