WeakMaps (WeakMap) (avançado) – JavaScript para programadores impacientes (edição ES2021)
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.
Set
)