JavaScript WeakMap y WeakSet

En el capítulo Garbage Collection, se afirmó que el motor de JavaScript puede almacenar un valor en la memoria una vez que es alcanzable.

Aquí hay un ejemplo:

Javascript remove object from memory
let js = { name: "Javascript" }; // object can be accessed, js is the reference to it // rewrite the reference js = null; // the object will be deleted from memory console.log(js);

Como regla general, las propiedades o elementos de estructuras de datos como un objeto o un array son alcanzables y se mantienen en la memoria una vez que esa estructura de datos está en la memoria. Por ejemplo, después de poner un objeto en un array, existirá mientras exista el array.

Aquí tienes un ejemplo:

Javascript cache a function result
let js = { name: "Javascript" }; let array = [js]; js = null; // rewrite the reference // js is stored in an array, so it will not garbage-collected // it can be received as an array[0] console.log(js);

Correspondientemente, al aplicar un objeto como la clave en un Map regular, existirá mientras exista el mapa.

Un ejemplo se vería así:

Javascript cache a function result
let js = { name: "Javascript" }; let map = new Map(); map.set(js, "..."); js = null; // rewriting the reference // js is on the map, // it can be received by using map.keys() console.log(js);

A continuación, vamos a cubrir WeakMap, que es completamente diferente y no se abstiene de la recolección de basura de los objetos clave.

WeakMap

La diferencia principal entre Map y WeakMap es que la clave de WeakMap no puede ser valores primitivos. Deben ser objetos, como en el ejemplo de abajo:

Javascript WeakMap
let weakMap = new WeakMap(); let obj = {}; key1 = weakMap.set(obj, "ok"); // works fine, object key console.log(key1); // can't use string as key key2 = weakMap.set("test", "Oops"); // Error because the “test” is not an object console.log(key2);

La iteración y los métodos keys(), values(), entries() no son compatibles con WeakMap.

Los métodos que sí son compatibles con WeakMap son los siguientes: weakMap.get(key), weakMap.set(key, value), weakMap.has(key) y weakMap.delete(key).

weakMap.delete(key)

El almacenamiento de datos adicionales es el uso principal para WeakMap. WeakMap es especialmente útil para almacenar datos asociados a una librería de terceros. Por ejemplo, considere almacenar los datos en un WeakMap, con un objeto como clave. Una vez que el objeto se recolecta como basura, los datos también desaparecerán automáticamente como se muestra a continuación:

let weakMap = new WeakMap();
let obj = {
  name: "test"
};
key = weakMap.set(obj, "test docs");
// if obj disappears, test docs will be automatically destroyed

Ahora, imagina tener un código que mantiene un recuento de visitas para los usuarios. Esta información se conserva dentro de un mapa, con un objeto de usuario como clave y el recuento de las visitas como valor.

Después de que un usuario se va, tienes la intención de dejar de almacenar su recuento de visitas.

Primero, veamos un ejemplo de función de conteo con Map. Se verá así:

Javascript WeakMap additional data
// visitsCount.js let visitsCountMap = new Map(); // map: book => visits count // increase the visits count function countBook(book) { let count = visitsCountMap.get(book) || 0; visitsCountMap.set(book, count + 1); console.log(count); } // main.js let js = { name: "Javascript" }; countBook(js); // count his visits countBook(js); // count his visits // later js leaves us js = null; console.log(js);

Por lo tanto, al eliminar a los usuarios, es necesario limpiar visitsCountMap. De lo contrario, se almacenará en la memoria de manera indefinida.

Sin embargo, limpiar de esta manera puede llegar a ser una tarea molesta a veces. Si quieres evitarlo, puedes recurrir a WeakMap. Aquí tienes un ejemplo de cómo usar WeakMap en lugar de ese tipo de limpieza:

Javascript WeakMap additional data
// visitsCount.js let visitsCountMap = new WeakMap(); // weakmap: book => visits count // increase the visits count function countBook(book) { let count = visitsCountMap.get(book) || 0; visitsCountMap.set(book, count + 1); console.log(count); } // main.js let js = { name: "Javascript" }; countBook(js); // count his visits countBook(js); // count his visits // later js leaves us js = null; console.log(js);

Ahora, ya no es necesario limpiar visitsCountMap.

Acerca del Caché

El caché ocurre cuando se debe recordar (almacenar en caché) el resultado de una función para reutilizarlo más tarde al llamar al mismo objeto.

Se puede usar Map para almacenar resultados de la siguiente manera:

Javascript cache a function result
// cache.js let cache = new Map(); // calculate and remember the result function process(myObj) { if (!cache.has(myObj)) { let res = /* result calculations for */ myObj; cache.set(myObj, res); } return cache.get(myObj); } // Now we use process() in another file: // main.js let myObj = { /* let's say we have an object */ }; let res1 = process(myObj); // calculated // later from another place in the code let res2 = process(myObj); // the result taken from the cache is remembered // later when an object is no longer needed: myObj = null; console.log(cache.size); // 1

En caso de llamar a process(obj) con el mismo objeto varias veces, calculará el resultado sólo la primera vez. Después, tomará la información de la caché.

La única desventaja del caché es que necesitas limpiar la caché una vez que ya no necesites el objeto.

Reemplazar Map con WeakMap resolverá el problema. La información en caché se eliminará automáticamente de la memoria una vez que el objeto se haya recolectado como basura.

Para ser más precisos, consideremos el ejemplo de abajo:

// cache.js
let cache = new WeakMap();
// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let res = /* calculate the result for */ obj;
    cache.set(obj, res);
  }
  return cache.get(obj);
}
 
// main.js
let obj = { /* some object */ };
let res1 = process(obj);
let res2 = process(obj);
// later, when object is no longer needed:
obj = null;
// Can not get cache.size because it is  WeakMap,
// but it is 0 or soon will be 0
//Once object gets garbage collected, the cached data will also be cleaned

WeakSet

WeakSet se considera equivalente a Set. Sin embargo, sólo los objetos y no los primitivos pueden añadirse a WeakSet.

Un objeto se encuentra en el conjunto mientras es alcanzable en otro lugar.

WeakSet también admite has, add y delete. Pero no se admiten iteraciones ni los métodos size y keys().

También puede servir como almacenamiento adicional para datos, pero no para datos arbitrarios. Aquí hay un ejemplo de cómo agregar lenguajes a WeakSet para llevar un registro de los que crearon el sitio:

Javascript WeakSet
let createdSet = new WeakSet(); let html = { name: "Html" }; let php = { name: "php" }; let js = { name: "Javascript" }; createdSet.add(html); // Html created us createdSet.add(js); // Then Javascript createdSet.add(html); // Html again // createdSet has 2 languages now // check if html created? console.log(createdSet.has(html)); // true // check if php created? console.log(createdSet.has(php)); // false html = null; // createdSet will be cleaned automatically

Hay una limitación significativa en WeakMap y WeakSet: no hay iteraciones. Tampoco existe la habilidad de recibir todo el contenido actual.

Resumen

En este capítulo, cubrimos WeakMap y WeakSet.

Para resumir, podemos afirmar que WeakMap se considera una colección similar a Map, que solo permite que los objetos sean claves. Los elimina junto con el valor asociado cuando se vuelven inaccesibles.

WeakSet se considera una colección similar a Set, que almacena solo objetos y los elimina cuando se vuelven inaccesibles.

Ambas colecciones no admiten propiedades ni métodos que no se refieren a todas las claves o al recuento de las mismas. Sólo permiten operaciones individuales.

Hora del Cuestionario: ¡Pon a Prueba Tus Habilidades!

¿Listo para desafiar lo que has aprendido? Sumérgete en nuestros cuestionarios interactivos para una comprensión más profunda y una forma divertida de reforzar tu conocimiento.

¿Te resulta útil?