Quais são os usos reais do ES6 WeakMap?

Quais são os usos reais da estrutura de dados WeakMap introduzidos no ECMAScript 6?

Como uma chave de um mapa fraco cria uma forte referência ao seu valor correspondente, garantindo que um valor que tenha sido inserido em um mapa fraco nunca desapareça enquanto sua chave ainda estiver viva, ele não pode ser usado para tabelas de notas, caches ou qualquer outra coisa que você normalmente usaria referências fracas, mapas com valores fracos, etc. para.

Parece-me que isso:

 weakmap.set(key, value); 

… é apenas uma maneira indireta de dizer isso:

 key.value = value; 

Que casos de uso concretos estou faltando?

Fundamentalmente

Os WeakMaps fornecem uma maneira de estender objects de fora sem interferir na garbage collection. Sempre que você quiser estender um object, mas não puder, porque ele está selado – ou de uma fonte externa – um WeakMap pode ser aplicado.

Um WeakMap é um mapa (dictionary) onde as chaves são fracas – isto é, se todas as referências à chave forem perdidas e não houver mais referências ao valor – o valor pode ser coletado como lixo. Vamos mostrar isso primeiro através de exemplos, depois explicar um pouco e finalmente terminar com um uso real.

Digamos que eu esteja usando uma API que me dê um certo object:

 var obj = getObjectFromLibrary(); 

Agora, eu tenho um método que usa o object:

 function useObj(obj){ doSomethingWith(obj); } 

Quero acompanhar quantas vezes o método foi chamado com um determinado object e reportar se acontecer mais de N vezes. Ingenuamente, alguém pensaria em usar um mapa:

 var map = new Map(); // maps can have object keys function useObj(obj){ doSomethingWith(obj); var called = map.get(obj) || 0; called++; // called one more time if(called > 10) report(); // Report called more than 10 times map.set(obj, called); } 

Isso funciona, mas há um memory leaks – agora rastreamos cada object de biblioteca passado para a function que impede que os objects da biblioteca sejam coletados como lixo. Em vez disso – podemos usar um WeakMap :

 var map = new WeakMap(); // create a weak map function useObj(obj){ doSomethingWith(obj); var called = map.get(obj) || 0; called++; // called one more time if(called > 10) report(); // Report called more than 10 times map.set(obj, called); } 

E o memory leaks se foi.

Casos de uso

Alguns casos de uso que causariam um memory leaks e são habilitados pelo WeakMap incluem:

  • Mantendo dados privados sobre um object específico e dando access apenas a pessoas com uma referência ao Mapa. Uma abordagem mais ad-hoc está chegando com a proposta de símbolos privados, mas isso é muito tempo a partir de agora.
  • Manter dados sobre objects de biblioteca sem alterá-los ou incorrer em sobrecarga.
  • Manter dados sobre um pequeno conjunto de objects onde existem muitos objects do tipo para não incorrer em problemas com classs ocultas que os mecanismos JS usam para objects do mesmo tipo.
  • Mantendo dados sobre objects do host, como nós DOM no navegador.
  • Adicionando um recurso a um object do lado de fora (como o exemplo do emissor de evento na outra resposta).

Vamos olhar para um uso real

Pode ser usado para estender um object do lado de fora. Vamos dar um exemplo prático (adaptado, meio real – para fazer um ponto) do mundo real do Node.js.

Vamos dizer que você é o Node.js e você tem objects Promise – agora você quer acompanhar todas as promises atualmente rejeitadas – no entanto, você não quer impedi-los de serem coletados como lixo, caso não existam referências a eles.

Agora, você não quer adicionar propriedades a objects nativos por razões óbvias – então você está preso. Se você mantiver referências às promises, estará causando memory leaks, já que nenhuma garbage collection pode acontecer. Se você não mantiver referências, não poderá salvar informações adicionais sobre promises individuais. Qualquer esquema que envolva salvar o ID de uma promise inerentemente significa que você precisa de uma referência a ela.

Digite WeakMaps

WeakMaps significa que as chaves são fracas. Não há maneiras de enumerar um mapa fraco ou obter todos os seus valores. Em um mapa fraco, você pode armazenar os dados com base em uma chave e, quando a chave é coletada como lixo, os valores também são armazenados.

Isto significa que, dada uma promise, você pode armazenar o estado sobre ela – e esse object ainda pode ser coletado como lixo. Mais tarde, se você obtiver uma referência a um object, poderá verificar se tem algum estado relacionado a ele e relatá-lo.

Isto foi usado para implementar ganchos de rejeição não manipulados por Petka Antonov como este :

 process.on('unhandledRejection', function(reason, p) { console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason); // application specific logging, throwing an error, or other logic here }); 

Mantemos informações sobre promises em um mapa e podemos saber quando uma promise rejeitada foi tratada.

Essa resposta parece ser tendenciosa e inutilizável em um cenário do mundo real. Por favor, leia-o como está, e não o considere como uma opção real para qualquer outra coisa que não seja experimentação

Um caso de uso poderia ser usá-lo como um dictionary para ouvintes, eu tenho um colega de trabalho que fez isso. É muito útil porque qualquer ouvinte é diretamente direcionado a essa maneira de fazer as coisas. Adeus listener.on .

Mas de um ponto de vista mais abstrato, o WeakMap é especialmente poderoso para desmaterializar o access a basicamente qualquer coisa, você não precisa de um namespace para isolar seus membros, uma vez que já está implícito na natureza dessa estrutura. Tenho certeza que você poderia fazer algumas grandes melhorias de memory, substituindo chaves de objects redundantes (mesmo que a desconstrução faça o trabalho para você).


Antes de ler o que vem a seguir

Agora percebo que minha ênfase não é exatamente a melhor maneira de resolver o problema e, como Benjamin Gruenbaum apontou (confira sua resposta, se já não está acima da minha: p), esse problema não poderia ter sido resolvido com um Map comum. já que teria vazado, assim, a força principal do WeakMap é que ele não interfere na garbage collection, já que eles não mantêm uma referência.


Aqui está o código real do meu colega de trabalho (graças a ele por compartilhar)

Fonte completa aqui , é sobre o gerenciamento de ouvintes que eu falei acima (você também pode dar uma olhada nas especificações )

 var listenableMap = new WeakMap(); export function getListenable (object) { if (!listenableMap.has(object)) { listenableMap.set(object, {}); } return listenableMap.get(object); } export function getListeners (object, identifier) { var listenable = getListenable(object); listenable[identifier] = listenable[identifier] || []; return listenable[identifier]; } export function on (object, identifier, listener) { var listeners = getListeners(object, identifier); listeners.push(listener); } export function removeListener (object, identifier, listener) { var listeners = getListeners(object, identifier); var index = listeners.indexOf(listener); if(index !== -1) { listeners.splice(index, 1); } } export function emit (object, identifier, ...args) { var listeners = getListeners(object, identifier); for (var listener of listeners) { listener.apply(object, args); } } 

WeakMap funciona bem para encapsulamento e ocultação de informações

WeakMap está disponível apenas para ES6 e acima. Um WeakMap é uma coleção de pares chave e valor, onde a chave deve ser um object. No exemplo a seguir, criamos um WeakMap com dois itens:

 var map = new WeakMap(); var pavloHero = {first: "Pavlo", last: "Hero"}; var gabrielFranco = {first: "Gabriel", last: "Franco"}; map.set(pavloHero, "This is Hero"); map.set(gabrielFranco, "This is Franco"); console.log(map.get(pavloHero));//This is Hero 

Usamos o método set() para definir uma associação entre um object e outro item (uma string no nosso caso). Usamos o método get() para recuperar o item associado a um object. O aspecto interessante do WeakMap s é o fato de ele WeakMap uma referência fraca à chave dentro do mapa. Uma referência fraca significa que, se o object for destruído, o coletor de lixo removerá toda a input do WeakMap , liberando memory.

 var TheatreSeats = (function() { var priv = new WeakMap(); var _ = function(instance) { return priv.get(instance); }; return (function() { function TheatreSeatsConstructor() { var privateMembers = { seats: [] }; priv.set(this, privateMembers); this.maxSize = 10; } TheatreSeatsConstructor.prototype.placePerson = function(person) { _(this).seats.push(person); }; TheatreSeatsConstructor.prototype.countOccupiedSeats = function() { return _(this).seats.length; }; TheatreSeatsConstructor.prototype.isSoldOut = function() { return _(this).seats.length >= this.maxSize; }; TheatreSeatsConstructor.prototype.countFreeSeats = function() { return this.maxSize - _(this).seats.length; }; return TheatreSeatsConstructor; }()); })() 

Eu uso WeakMap para o cache de WeakMap preocupações de funções que levam em objects imutáveis ​​como seu parâmetro.

Memoization é uma maneira sofisticada de dizer “depois de calcular o valor, armazene em cache para que você não precise computá-lo novamente”.

Aqui está um exemplo:

 // using immutable.js from here https://facebook.github.io/immutable-js/ const memo = new WeakMap(); let myObj = Immutable.Map({a: 5, b: 6}); function someLongComputeFunction (someImmutableObj) { // if we saved the value, then return it if (memo.has(someImmutableObj)) { console.log('used memo!'); return memo.get(someImmutableObj); } // else compute, set, and return const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b'); memo.set(someImmutableObj, computedValue); console.log('computed value'); return computedValue; } someLongComputeFunction(myObj); someLongComputeFunction(myObj); someLongComputeFunction(myObj); // reassign myObj = Immutable.Map({a: 7, b: 8}); someLongComputeFunction(myObj); 
  

Mapas fracos podem ser usados ​​para armazenar metadados sobre elementos DOM sem interferir na garbage collection ou deixar colegas de trabalho irritados com seu código. Por exemplo, você poderia usá-los para indexar todos os elementos em uma página da Web.

Sem WeakMaps ou WeakSets:

 var elements = document.getElementsByTagName('*'), i = -1, len = elements.length; while (++i !== len) { // Production code written this poorly makes me want to cry: elements[i].lookupindex = i; elements[i].elementref = []; elements[i].elementref.push( elements[Math.pow(i, 2) % len] ); } // Then, you can access the lookupindex's // For those of you new to javascirpt, I hope the comments below help explain // how the ternary operator (?:) works like an inline if-statement document.write(document.body.lookupindex + '
' + ( (document.body.elementref.indexOf(document.currentScript) !== -1) ? // if(document.body.elementref.indexOf(document.currentScript) !== -1){ "true" : // } else { "false" ) // } );