|
| 1 | +# WeakMap y WeakSet |
| 2 | + |
| 3 | +Como sabemos por el capítulo <info:garbage-collection>, el motor de JavaScript almacena un valor en la memoria mientras es accesible(y puede ser potencialmente usado). |
| 4 | + |
| 5 | +Por ejemplo: |
| 6 | +```js |
| 7 | +let john = { name: "John" }; |
| 8 | + |
| 9 | +// se puede acceder al objeto, john es su referencia |
| 10 | + |
| 11 | +// sobrescribe la referencia |
| 12 | +john = null; |
| 13 | + |
| 14 | +*!* |
| 15 | +// el objeto será eliminado de la memoria |
| 16 | +*/!* |
| 17 | +``` |
| 18 | + |
| 19 | +Por lo general, las propiedades de un objeto o elementos de un array u otra estructura de datos se consideran accesibles y se mantienen en la memoria mientras esa estructura de datos permanezca en la memoria. |
| 20 | + |
| 21 | +Por ejemplo, si colocamos un objeto en un array, mientras el array esté vivo, el objeto también estará vivo, incluso si no hay otras referencias a él. |
| 22 | + |
| 23 | +Como esto: |
| 24 | + |
| 25 | +```js |
| 26 | +let john = { name: "John" }; |
| 27 | + |
| 28 | +let array = [ john ]; |
| 29 | + |
| 30 | +john = null; // sobrescribe la referencia |
| 31 | + |
| 32 | +*!* |
| 33 | +// John se almacena dentro del array, por lo que no será recolectado por el recolector de basura |
| 34 | +// Lo podemos obtener como array[0] |
| 35 | +*/!* |
| 36 | +``` |
| 37 | + |
| 38 | +Similar a eso, si usamos un objeto como la propiedad en un `Map` regular, entonces mientras exista el` Map`, ese objeto también existe. Este objeto ocupa memoria y no puede ser reclamado por el recolector de basura. |
| 39 | + |
| 40 | +Por ejemplo |
| 41 | + |
| 42 | +```js |
| 43 | +let john = { name: "John" }; |
| 44 | + |
| 45 | +let map = new Map(); |
| 46 | +map.set(john, "..."); |
| 47 | + |
| 48 | +john = null; // sobreescribe la referencia |
| 49 | + |
| 50 | +*!* |
| 51 | +// john se almacena dentro de map, |
| 52 | +// podemos obtenerlo usando map.keys () |
| 53 | +*/!* |
| 54 | +``` |
| 55 | + |
| 56 | +`WeakMap` es fundamentalmente diferente en este aspecto. No impide la recolección de basura de propiedades de objetos. |
| 57 | + |
| 58 | +Veamos qué significa esto en los ejemplos. |
| 59 | + |
| 60 | +## WeakMap |
| 61 | + |
| 62 | +La primera diferencia con `Map` es que las propiedades `WeakMap` deben ser objetos, no valores primitivos: |
| 63 | + |
| 64 | +```js run |
| 65 | +let weakMap = new WeakMap(); |
| 66 | + |
| 67 | +let obj = {}; |
| 68 | + |
| 69 | +weakMap.set(obj, "ok"); // funciona bien (propiedad de objeto) |
| 70 | + |
| 71 | +*!* |
| 72 | +// no puede usar un string como propiedad |
| 73 | +weakMap.set("test", "Whoops"); // Error, porque "test" no es un objeto |
| 74 | +*/!* |
| 75 | +``` |
| 76 | + |
| 77 | +Ahora, si usamos un objeto como propiedad y no hay otras referencias a ese objeto, se eliminará de la memoria (y del map) automáticamente. |
| 78 | + |
| 79 | +```js |
| 80 | +let john = { name: "John" }; |
| 81 | + |
| 82 | +let weakMap = new WeakMap(); |
| 83 | +weakMap.set(john, "..."); |
| 84 | + |
| 85 | +john = null; // sobreescribe la referencia |
| 86 | + |
| 87 | +// ¡John se eliminó de la memoria! |
| 88 | +``` |
| 89 | + |
| 90 | +Compárelo con el ejemplo del `Map` regular anterior. Ahora, si `john` solo existe como la propiedad de` WeakMap`, se eliminará automáticamente del map (y de la memoria). |
| 91 | + |
| 92 | +`WeakMap` no admite la iteración ni los métodos `keys()`, `values()`, `entries()`, así que no hay forma de obtener todas las propiedades o valores de él. |
| 93 | + |
| 94 | +`WeakMap` tiene solo los siguientes métodos: |
| 95 | + |
| 96 | +- `weakMap.get(propiedad)` |
| 97 | +- `weakMap.set(propiedad, valor)` |
| 98 | +- `weakMap.delete(propiedad)` |
| 99 | +- `weakMap.has(propiedad)` |
| 100 | + |
| 101 | +¿Por qué tanta limitación? Eso es por razones técnicas. Si un objeto ha perdido todas las demás referencias (como `john` en el código anterior), entonces se debe recolectar automáticamente como basura. Pero técnicamente no se especifica exactamente *cuándo se realiza la limpieza*. |
| 102 | + |
| 103 | +El motor de JavaScript decide eso. Puede optar por realizar la limpieza de la memoria inmediatamente o esperar y realizar la limpieza más tarde cuando ocurran más eliminaciones. Por lo tanto, técnicamente no se conoce el recuento actual de elementos de un `WeakMap`. El motor puede haberlo limpiado o no, o lo hizo parcialmente. Por esa razón, los métodos que acceden a todas las propiedades / valores no son compatibles. |
| 104 | + |
| 105 | +Ahora, ¿dónde necesitamos esta estructura de datos? |
| 106 | + |
| 107 | +## Caso de uso: datos adicionales |
| 108 | + |
| 109 | +El área principal de aplicación de `WeakMap` es en un *almacenamiento de datos adicional*. |
| 110 | + |
| 111 | +Si estamos trabajando con un objeto que "pertenece" a otro código, tal vez incluso una biblioteca de terceros, y quisiera almacenar algunos datos asociados con él, eso solo debería existir mientras el objeto esté vivo, entonces `WeakMap` es exactamente lo que se necesita. |
| 112 | + |
| 113 | +Ponemos los datos en un `WeakMap`, utilizando el objeto como propiedad, y cuando el objeto sea recolectado por el recolector de basura, esos datos también desaparecerán automáticamente. |
| 114 | + |
| 115 | +```js |
| 116 | +weakMap.set(john, "secret documents"); |
| 117 | +// si John muere, secret documents será destruido automáticamente |
| 118 | +``` |
| 119 | + |
| 120 | +Veamos un ejemplo. |
| 121 | + |
| 122 | +Por ejemplo, tenemos un código que mantiene un recuento de visitas para los usuarios. La información se almacena en un mapa: un objeto de usuario es la propiedad y el recuento de visitas es el valor. Cuando un usuario se va (su objeto será recolectado por el recolector de basura), ya no queremos almacenar su recuento de visitas. |
| 123 | + |
| 124 | +Aquí hay un ejemplo de una función de conteo con `Map`: |
| 125 | + |
| 126 | +```js |
| 127 | +// 📁 visitsCount.js |
| 128 | +let visitsCountMap = new Map(); // map: user => visits count |
| 129 | + |
| 130 | +// incrementar el recuento de visitas |
| 131 | +function countUser(user) { |
| 132 | + let count = visitsCountMap.get(user) || 0; |
| 133 | + visitsCountMap.set(user, count + 1); |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +Y aquí hay otra parte del código, tal vez otro archivo usándolo: |
| 138 | + |
| 139 | +```js |
| 140 | +// 📁 main.js |
| 141 | +let john = { name: "John" }; |
| 142 | + |
| 143 | +countUser(john); // cuenta sus visitas |
| 144 | + |
| 145 | +// luego John nos deja |
| 146 | +john = null; |
| 147 | +``` |
| 148 | + |
| 149 | +Ahora el objeto `john` debería ser recolectado como basura, pero permanece en la memoria, ya que es una propiedad en` visitCountMap`. |
| 150 | + |
| 151 | +Necesitamos limpiar `visitCountMap` cuando eliminamos usuarios, de lo contrario, crecerá en la memoria indefinidamente. Tal limpieza puede convertirse en una tarea tediosa en arquitecturas complejas. |
| 152 | + |
| 153 | +Lo podemos evitar cambiando a `WeakMap` en su lugar: |
| 154 | + |
| 155 | +```js |
| 156 | +// 📁 visitsCount.js |
| 157 | +let visitsCountMap = new WeakMap(); // weakmap: user => visits count |
| 158 | + |
| 159 | +// incrementar el recuento de visitas |
| 160 | +function countUser(user) { |
| 161 | + let count = visitsCountMap.get(user) || 0; |
| 162 | + visitsCountMap.set(user, count + 1); |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +Ahora no tenemos que limpiar `visitasCountMap`. Después de que el objeto `john` se vuelve inalcanzable por todos los medios excepto como una propiedad de` WeakMap`, se elimina de la memoria, junto con la información de esa propiedad de `WeakMap`. |
| 167 | + |
| 168 | +## Caso de uso: almacenamiento en caché |
| 169 | + |
| 170 | +Otro ejemplo común es el almacenamiento en caché: cuando se debe recordar el resultado de una función ("en caché"), para que las llamadas futuras en el mismo objeto lo reutilicen. |
| 171 | + |
| 172 | +Podemos usar `Map` para almacenar resultados, así: |
| 173 | + |
| 174 | +```js run |
| 175 | +// 📁 cache.js |
| 176 | +let cache = new Map(); |
| 177 | + |
| 178 | +// calcular y recordar el resultado |
| 179 | +function process(obj) { |
| 180 | + if (!cache.has(obj)) { |
| 181 | + let result = /* calculations of the result for */ obj; |
| 182 | + |
| 183 | + cache.set(obj, result); |
| 184 | + } |
| 185 | + |
| 186 | + return cache.get(obj); |
| 187 | +} |
| 188 | + |
| 189 | +*!* |
| 190 | +// Ahora nosotros usamos process() en otro archivo: |
| 191 | +*/!* |
| 192 | + |
| 193 | +// 📁 main.js |
| 194 | +let obj = {/* let's say we have an object */}; |
| 195 | + |
| 196 | +let result1 = process(obj); // calculated |
| 197 | + |
| 198 | +// ...después, en otro lugar del código... |
| 199 | +let result2 = process(obj); // resultado recordado tomado de la memoria caché |
| 200 | + |
| 201 | +// ...después, cuando el objeto no se necesitará nada más: |
| 202 | +obj = null; |
| 203 | + |
| 204 | +alert(cache.size); // 1 (Ouch! ¡El objeto todavía está en caché, tomando memoria!) |
| 205 | +``` |
| 206 | + |
| 207 | +Para múltiples llamadas de `proceso (obj)` con el mismo objeto, solo calcula el resultado la primera vez, y luego lo toma de `caché`. La desventaja es que necesitamos limpiar el 'caché' cuando el objeto ya no es necesario. |
| 208 | + |
| 209 | +Si reemplazamos `Map` por `WeakMap`, este problema desaparece: el resultado en caché se eliminará de la memoria automáticamente después de que el objeto se recolecte. |
| 210 | + |
| 211 | +```js run |
| 212 | +// 📁 cache.js |
| 213 | +*!* |
| 214 | +let cache = new WeakMap(); |
| 215 | +*/!* |
| 216 | + |
| 217 | +// calcular y recordad el resultado |
| 218 | +function process(obj) { |
| 219 | + if (!cache.has(obj)) { |
| 220 | + let result = /* calcular el resultado para */ obj; |
| 221 | + |
| 222 | + cache.set(obj, result); |
| 223 | + } |
| 224 | + |
| 225 | + return cache.get(obj); |
| 226 | +} |
| 227 | + |
| 228 | +// 📁 main.js |
| 229 | +let obj = {/* some object */}; |
| 230 | + |
| 231 | +let result1 = process(obj); |
| 232 | +let result2 = process(obj); |
| 233 | + |
| 234 | +// ...después, cuando el objeto no se necesitará más: |
| 235 | +obj = null; |
| 236 | + |
| 237 | +// No se puede obtener cache.size, ya que es un WeakMap, |
| 238 | +// pero es 0 o pronto será 0 |
| 239 | +// Cuando obj obtiene basura recolectada, los datos en caché también se eliminarán |
| 240 | +``` |
| 241 | + |
| 242 | +## WeakSet |
| 243 | + |
| 244 | +`WeakSet` se comporta de manera similar: |
| 245 | + |
| 246 | +- Es análogo a `Set`, pero solo podemos agregar objetos a `WeakSet` (no primitivos). |
| 247 | +- Existe un objeto en el conjunto mientras es accesible desde otro lugar. |
| 248 | +- Al igual que `Set`, admite` add`, `has` y` delete`, pero no `size`,` keys()` ni iteraciones. |
| 249 | + |
| 250 | +Al ser "débil", también sirve como almacenamiento adicional. Pero no para datos arbitrarios, sino para hechos "sí / no". Una membresía en `WeakSet` puede significar algo sobre el objeto. |
| 251 | + |
| 252 | +Por ejemplo, podemos agregar usuarios a `WeakSet` para realizar un seguimiento de los que visitaron nuestro sitio: |
| 253 | + |
| 254 | +```js run |
| 255 | +let visitedSet = new WeakSet(); |
| 256 | + |
| 257 | +let john = { name: "John" }; |
| 258 | +let pete = { name: "Pete" }; |
| 259 | +let mary = { name: "Mary" }; |
| 260 | + |
| 261 | +visitedSet.add(john); // John nos visita |
| 262 | +visitedSet.add(pete); // luego Pete |
| 263 | +visitedSet.add(john); // John otra vez |
| 264 | + |
| 265 | +// visitedSet tiene 2 usuarios ahora |
| 266 | + |
| 267 | +// comprobar si John nos visitó? |
| 268 | +alert(visitedSet.has(john)); // true |
| 269 | + |
| 270 | +// comprobar si Mary nos visitó? |
| 271 | +alert(visitedSet.has(mary)); // false |
| 272 | + |
| 273 | +john = null; |
| 274 | + |
| 275 | +// visitedSet se limpiará automáticamente |
| 276 | +``` |
| 277 | + |
| 278 | + La limitación más notable de `WeakMap` y` WeakSet` es la ausencia de iteraciones y la imposibilidad de obtener todo el contenido actual. Esto puede parecer inconveniente, pero no impide que `WeakMap / WeakSet` haga su trabajo principal: ser un almacenamiento "adicional" de datos para objetos que se almacenan / administran en otro lugar. |
| 279 | + |
| 280 | +## Resumen |
| 281 | + |
| 282 | +`WeakMap` es una colección similar a `Map` que permite solo objetos como propiedades y los elimina junto con el valor asociado una vez que se vuelven inaccesibles por otros medios. |
| 283 | + |
| 284 | +`WeakSet` es una colección tipo `Set` que almacena solo objetos y los elimina una vez que se vuelven inaccesibles por otros medios. |
| 285 | + |
| 286 | +Ambos no admiten métodos y propiedades que se refieren a todas las propiedades o su recuento. Solo se permiten operaciones individuales. |
| 287 | + |
| 288 | +`WeakMap` y` WeakSet` se utilizan como estructuras de dato "secundarias" además del almacenamiento de objetos "principal". Una vez que el objeto se elimina del almacenamiento principal, si solo se encuentra como la propiedad de `WeakMap` o en un` WeakSet`, se limpiará automáticamente. |
0 commit comments