Articles

WeakMaps (WeakMap) (avançado) – JavaScript para programadores impacientes (edição ES2021)

(Anúncio, por favor não bloqueie.)

34 WeakMaps (WeakMap) (avançado)

  • 34.1 WeakMaps são caixas pretas
  • 34.2 As chaves de um WeakMap são fracamente mantidas
    • 34.2.1 Todas as chaves WeakMap devem ser objetos
    • 34.2.2 Use caixa: anexando valores a objetos
  • 34.3 Exemplos
    • 34.3.1 Cache de resultados computados via WeakMaps
    • 34.3.2 Manter dados privados em WeakMaps
  • 34.4 WeakMap API

WeakMaps são semelhantes a Mapas, com as seguintes diferenças:

  • São caixas pretas, onde um valor só pode ser acessado se você tiver o WeakMap e a chave.
  • As chaves de um WeakMap são fracamente mantidas: se um objeto é uma chave em um WeakMap, ele ainda pode ser coletado em lixo. Isso nos permite usar WeakMaps para anexar dados a objetos.

As duas próximas seções examinam com mais detalhes o que isso significa.

34.1 WeakMaps são caixas pretas

É impossível inspecionar o que está dentro de um WeakMap:

  • Por exemplo, você não pode iterar ou passar por cima de chaves, valores ou entradas. E você não pode computar o tamanho.
  • Adicionalmente, você também não pode limpar um WeakMap – você tem que criar uma nova instância.

Estas restrições permitem uma propriedade de segurança. Citando Mark Miller:

O mapeamento a partir do valor do par weakmap/key pair só pode ser observado ou afetado por alguém que tenha tanto o weakmap quanto a chave. Com clear(), alguém com apenas o WeakMap teria sido capaz de afetar o WeakMap-and-key-to-value mapping.

34.2 As chaves de um WeakMap são fracamente mantidas

As chaves de um WeakMap são ditas ser fracamente mantidas: Normalmente, se um objeto se refere a outro, então o último objeto não pode ser coletado com o lixo enquanto o primeiro existir. Com um WeakMap, isso é diferente: Se um objeto é uma chave e não se refere a outro objeto, ele pode ser recolhido enquanto o WeakMap ainda existir. Isso também leva a que a entrada correspondente seja removida (mas não há como observar isso).

34.2.1 Todas as chaves WeakMap devem ser objetos

Todas as chaves WeakMap devem ser objetos. Você recebe um erro se usar um valor primitivo:

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

Com valores primitivos como chaves, os WeakMaps não seriam mais caixas pretas. Mas dado que os valores primitivos nunca são coletados como lixo, você não lucra com chaves fracas de qualquer maneira, e pode usar um mapa normal.

34.2.2 Use case: anexando valores a objetos

Este é o principal caso de uso para WeakMaps: você pode usá-los para anexar valores externamente a objetos – por exemplo:

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

Na linha A, anexamos um valor a obj. Na linha B, obj já pode ser coletado o garbage-collected, mesmo que wm ainda exista. Esta técnica de anexar um valor a um objeto é equivalente a uma propriedade desse objeto sendo armazenada externamente. Se wm fosse uma propriedade, o código anterior teria a seguinte aparência:

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

34.3 Exemplos

34.3.1 Caching de resultados computados via WeakMaps

Com WeakMaps, você pode associar resultados previamente computados com objetos sem ter que se preocupar com gerenciamento de memória. A seguinte função countOwnKeys() é um exemplo: ela armazena resultados anteriores no 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 ; }}

Se usarmos esta função com um objeto obj, você pode ver que o resultado só é computado para a primeira invocação, enquanto um valor em cache é usado para a segunda invocação:

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

34.3.2 Mantendo dados privados em WeakMaps

No seguinte código, os WeakMaps _counter e _action são usados para armazenar os valores de propriedades virtuais de instâncias 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()), );

É assim que Countdown é usado:

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);

Exercício: WeakMaps para dados privados

exercises/weakmaps/weakmaps_private_data_test.mjs

34.4 WeakMap API

O construtor e os quatro métodos de WeakMap trabalham da mesma forma que os seus Map equivalentes:

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

Questionário

Ver aplicação do Questionário.

Próximo: 35 Sets (Set)