Articles

WeakMaps (`WeakMap`) (avanzado) – JavaScript para programadores impacientes (edición ES2021)

(Anuncio, por favor no bloquee.)

34 WeakMaps (WeakMap) (avanzado)

  • 34.1 Los WeakMaps son cajas negras
  • 34.2 Las claves de un WeakMap se sostienen débilmente
  • 34.2.1 Todas las claves del WeakMap deben ser objetos
  • 34.2.2 Caso de uso: adjuntar valores a objetos
  • 34.3 Ejemplos
  • 34.3.1 Almacenamiento en caché de resultados calculados mediante WeakMaps
  • 34.3.2 Conservación de datos privados en WeakMaps
  • 34.4 API de WeakMap

    Los WeakMaps son similares a los Mapas, con las siguientes diferencias:

    • Son cajas negras, en las que sólo se puede acceder a un valor si se tiene tanto el WeakMap como la clave.
    • Las claves de un WeakMap se mantienen débilmente: si un objeto es una clave en un WeakMap, aún puede ser recogido por la basura. Esto nos permite utilizar los WeakMaps para adjuntar datos a los objetos.

    Las dos secciones siguientes examinan con más detalle lo que esto significa.

    34.1 Los WeakMaps son cajas negras

    Es imposible inspeccionar lo que hay dentro de un WeakMap:

    • Por ejemplo, no puedes iterar o hacer un bucle sobre las claves, valores o entradas. Y no puedes calcular el tamaño.
    • Además, tampoco puedes borrar un WeakMap – tienes que crear una instancia nueva.

    Estas restricciones permiten una propiedad de seguridad. Citando a Mark Miller:

    El mapeo del valor del par weakmap/clave sólo puede ser observado o afectado por alguien que tenga tanto el weakmap como la clave. Con clear(), alguien con sólo el WeakMap habría podido afectar al mapeo WeakMap-y-clave-valor.

    34.2 Las claves de un WeakMap se mantienen débilmente

    Se dice que las claves de un WeakMap se mantienen débilmente: Normalmente, si un objeto hace referencia a otro, este último no puede ser recogido mientras exista el primero. Con un WeakMap, esto es diferente: si un objeto es una clave y no se refiere a otra parte, puede ser recolectado mientras el WeakMap siga existiendo. Eso también hace que se elimine la entrada correspondiente (pero no hay forma de observarlo).

    34.2.1 Todas las claves WeakMap deben ser objetos

    Todas las claves WeakMap deben ser objetos. Se obtiene un error si se utiliza un valor primitivo:

    > const wm = new WeakMap();> wm.set(123, 'test')TypeError: Invalid value used as weak map key

    Con valores primitivos como claves, los WeakMaps ya no serían cajas negras. Pero dado que los valores primitivos nunca se recogen de la basura, no te beneficias de las claves débiles de todos modos, y puedes utilizar un Mapa normal.

    34.2.2 Caso de uso: adjuntar valores a los objetos

    Este es el principal caso de uso de los WeakMaps: puedes utilizarlos para adjuntar externamente valores a los objetos – por ejemplo:

    const wm = new WeakMap();{ const obj = {}; wm.set(obj, 'attachedValue'); // (A)}// (B)

    En la línea A, adjuntamos un valor a obj. En la línea B, obj ya se puede recoger la basura, aunque wm siga existiendo. Esta técnica de adjuntar un valor a un objeto es equivalente a una propiedad de ese objeto almacenada externamente. Si wm fuera una propiedad, el código anterior tendría el siguiente aspecto:

    { const obj = {}; obj.wm = 'attachedValue';}

    34.3 Ejemplos

    34.3.1 Almacenamiento en caché de resultados calculados mediante WeakMaps

    Con WeakMaps, se pueden asociar resultados calculados previamente con objetos sin tener que preocuparse de la gestión de la memoria. La siguiente función countOwnKeys() es un ejemplo: almacena en caché resultados anteriores en el WeakMap cache.

    const cache = new WeakMap();function countOwnKeys(obj) { if (cache.has(obj)) { return ; } else { const count = Object.keys(obj).length; cache.set(obj, count); return ; }}

    Si utilizamos esta función con un objeto obj, puedes ver que el resultado sólo se computa para la primera invocación, mientras que se utiliza un valor en caché para la segunda invocación:

    > const obj = { foo: 1, bar: 2};> countOwnKeys(obj)> countOwnKeys(obj)

    34.3.2 Mantener datos privados en WeakMaps

    En el siguiente código, los WeakMaps _counter y _action se utilizan para almacenar los valores de las propiedades virtuales de las instancias de Countdown:

    const _counter = new WeakMap();const _action = new WeakMap();class Countdown { constructor(counter, action) { _counter.set(this, counter); _action.set(this, action); } dec() { let counter = _counter.get(this); counter--; _counter.set(this, counter); if (counter === 0) { _action.get(this)(); } }}// The two pseudo-properties are truly private:assert.deepEqual( Object.keys(new Countdown()), );

    Así es como se utiliza Countdown:

    let invoked = false;const cd = new Countdown(3, () => invoked = true);cd.dec(); assert.equal(invoked, false);cd.dec(); assert.equal(invoked, false);cd.dec(); assert.equal(invoked, true);

    Ejercicio: WeakMaps para datos privados

    exercises/weakmaps/weakmaps_private_data_test.mjs

    34.4 API de WeakMap

    El constructor y los cuatro métodos de WeakMap funcionan igual que sus equivalentes de Map:

    • new WeakMap<K, V>(entries?: Iterable<>)
    • .delete(key: K) : boolean
    • .get(key: K) : V
    • .has(key: K) : boolean
    • .set(key: K, value: V) : this

    Quiz

    Ver aplicación quiz.

    Siguiente: 35 Sets (Set)