Qual é a diferença entre ES6 Map e WeakMap?

93

Olhando esta e esta páginas MDN, parece que a única diferença entre Maps e WeakMaps é uma propriedade de "tamanho" ausente para WeakMaps. Mas isso é verdade? Qual é a diferença entre eles?

Dmitrii Sorin
fonte
O efeito está no GC. Os WeakMaps podem ter suas chaves coletadas.
John Dvorak
@JanDvorak não há exemplo apontado no MDN sobre isso. Como aWeakMap.get (chave); // diga, 2 ... (ação GC) ... aWeakMap.get (chave); // digamos, indefinido
Dmitrii Sorin
1
Seu exemplo é impossível. keynão pode ser coletado, porque é referenciado por você.
John Dvorak
1
A decisão de design é que as ações do GC sejam invisíveis em Javascript. Você não pode observar GC fazendo seu trabalho.
John Dvorak
1
Consulte esta resposta relacionada para obter mais informações sobre este problema.
Benjamin Gruenbaum

Respostas:

53

Na mesma página, seção " Por que o mapa fraco ? " :

O experiente programador de JavaScript notará que esta API pode ser implementada em JavaScript com dois arrays (um para chaves, um para valores) compartilhados pelos 4 métodos API. Tal implementação teria dois inconvenientes principais. O primeiro é uma pesquisa O (n) (sendo n o número de chaves no mapa). O segundo é um problema de vazamento de memória. Com mapas escritos manualmente, a matriz de chaves manteria as referências a objetos-chave, evitando que fossem coletados como lixo. Em WeakMaps nativos, as referências a objetos-chave são mantidas "fracamente" , o que significa que não evitam a coleta de lixo no caso de não haver outra referência ao objeto.

Como as referências são fracas, as chaves do WeakMap não são enumeráveis ​​(ou seja, não há método que forneça uma lista das chaves). Se fossem, a lista dependeria do estado da coleta de lixo, introduzindo o não determinismo.

[E é por isso que eles também não têm sizepropriedade]

Se você deseja ter uma lista de chaves, deve mantê-la você mesmo. Existe também uma proposta ECMAScript com o objetivo de introduzir conjuntos e mapas simples que não usariam referências fracas e seriam enumeráveis.

- quais seriam os "normais" Maps . Não mencionados no MDN, mas na proposta harmonia , aqueles também têm items, keyse valuesmétodos de geradores e implementar a Iteratorinterface de .

Bergi
fonte
por isso new Map().get(x)tem aproximadamente o mesmo tempo look-up como a leitura de uma propriedade de um objeto simples?
Alexander Mills
1
@AlexanderMills Não vejo o que isso tem a ver com a pergunta, mas aqui estão alguns dados . Em geral, sim , eles são semelhantes , e você deve usar o apropriado .
Bergi
Então, meu entendimento é que o Map mantém um array interno para persistir sua chave por causa desse array. O coletor de lixo não consegue conter a referência. No WeekMap, ele não tem uma matriz onde as chaves são mantidas, portanto, a chave sem referência pode ser coletada como lixo.
Mohan Ram
@MohanRam A WeakMapainda tem um array (ou outra coleção) de entradas, ele apenas informa ao coletor de lixo que essas são referências fracas .
Bergi
Então, por que a iteração para chaves do WeekMap não é suportada?
Mohan Ram de
92

Ambos se comportam de maneira diferente quando um objeto referenciado por suas chaves / valores é excluído. Vamos pegar o código de exemplo abaixo:

var map = new Map();
var weakmap = new WeakMap();

(function(){
    var a = {x: 12};
    var b = {y: 12};

    map.set(a, 1);
    weakmap.set(b, 2);
})()

O IIFE acima é executado, não há como fazer referência {x: 12}e {y: 12}mais. O coletor de lixo vai em frente e exclui o ponteiro-chave b do “WeakMap” e também remove {y: 12}da memória. Mas no caso do “Mapa”, o coletor de lixo não remove um ponteiro do “Mapa” e também não remove {x: 12}da memória.

Resumo: WeakMap permite que o coletor de lixo faça sua tarefa, mas não Mapeie.

Referências: http://qnimate.com/difference-between-map-and-weakmap-in-javascript/

kshirish
fonte
12
Por que não é removido da memória? Porque você ainda pode fazer referência a ele! map.entries().next().value // [{x:12}, 1]
Bergi
4
Não é uma função auto-invocada. É uma expressão de função chamada imediatamente. benalman.com/news/2010/11/…
Olson.dev
então qual é a diferença entre mapa fraco e objeto
Muhammad Umer
@MuhammadUmer: o objeto pode ter apenas 'chaves' de string, enquanto WeakMapsó pode ter chaves não primitivas (sem strings ou números ou Symbols como chaves, apenas arrays, objetos, outros mapas, etc.).
Ahmed Fasih
1
@nnnnnn Sim, essa é a diferença, ainda está no, Map mas não noWeakMap
Alexander Derck
75

Talvez a próxima explicação seja mais clara para alguém.

var k1 = {a: 1};
var k2 = {b: 2};

var map = new Map();
var wm = new WeakMap();

map.set(k1, 'k1');
wm.set(k2, 'k2');

k1 = null;
map.forEach(function (val, key) {
    console.log(key, val); // k1 {a: 1}
});

k2 = null;
wm.get(k2); // undefined

Como você pode ver, após remover a k1chave da memória, ainda podemos acessá-la dentro do mapa. Ao mesmo tempo, remover a k2chave do WeakMap também a remove wmpor referência.

É por isso que o WeakMap não tem métodos enumeráveis ​​como forEach, porque não existe uma lista de chaves do WeakMap, elas são apenas referências a outros objetos.

Rax Wunter
fonte
10
na última linha, é claro, wm.get (null) será indefinido.
DaNeSh
8
Melhor resposta do que copiar e colar do site Mozilla, parabéns.
Joel Hernandez
2
no forEach, (key, val)deveria ser realmente(val, key)
Miguel Mota
inacreditável como um exemplo que não faz sentido consegue tantos
votos positivos
34

Outra diferença (fonte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap ):

Chaves de WeakMaps são apenas do tipo Object. Tipos de dados primitivos como chaves não são permitidos (por exemplo, um símbolo não pode ser uma chave WeakMap).

Nem uma string, número ou booleano podem ser usados ​​como uma WeakMapchave. A Map pode usar valores primitivos para chaves.

w = new WeakMap;
w.set('a', 'b'); // Uncaught TypeError: Invalid value used as weak map key

m = new Map
m.set('a', 'b'); // Works
Trevor Dixon
fonte
6
Caso alguém se pergunte: posso imaginar a razão por trás disso: você não pode manter ou passar referências a tipos primitivos. Portanto, a chave em um WeakMap seria sua única referência, sempre. Dessa forma, a coleta de lixo não seria possível. Não sei se referências fracas são impossíveis ou simplesmente não fazem sentido. Mas, de qualquer forma, a chave precisa ser algo que possa ser referenciado de maneira fraca.
Andreas Linnert
3

De Javascript.info

Mapa - se usarmos um objeto como a chave em um mapa normal, então, enquanto o mapa existir, esse objeto também existe. Ele ocupa memória e não pode ser coletado como lixo.

let john = { name: "John" };
let array = [ john ];
john = null; // overwrite the reference

// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]

Semelhante a isso, se usarmos um objeto como a chave em um Mapa normal, então, enquanto o Mapa existir, esse objeto também existe. Ele ocupa memória e não pode ser coletado

let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()

WeakMap - Agora, se usarmos um objeto como a chave nele, e não houver outras referências a esse objeto - ele será removido da memória (e do mapa) automaticamente.

let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference

// john is removed from memory!
Avadhut Thorat
fonte
3

WeapMap em javascript não contém nenhuma chave ou valor, ele apenas manipula o valor da chave usando um id único e define uma propriedade para o objeto chave.

porque define a propriedade key objectpor método Object.definePropert(), a chave não deve ser do tipo primitivo .

e também porque WeapMap não contém pares de valores-chave, não podemos obter a propriedade de comprimento de mapa fraco.

e também o valor manipulado é atribuído de volta ao objeto-chave, o coletor de lixo pode facilmente coletar a chave se não estiver em uso.

Código de amostra para implementação.

if(typeof WeapMap != undefined){
return;
} 
(function(){
   var WeapMap = function(){
      this.__id = '__weakmap__';
   }
        
   weakmap.set = function(key,value){
       var pVal = key[this.__id];
        if(pVal && pVal[0] == key){
           pVal[1]=value;
       }else{
          Object.defineProperty(key, this.__id, {value:[key,value]});
          return this;
        }
   }

window.WeakMap = WeakMap;
})();

referência de implementação

Ravi Sevta
fonte
1
Para ser claro, essa implementação funciona apenas pela metade. Não permite usar o mesmo objeto como chave em vários mapas fracos. Também não funciona para objetos congelados. E, claro, ele vaza o mapeamento para qualquer pessoa que tenha uma referência ao objeto. O primeiro pode ser corrigido por meio de símbolos, mas não os dois últimos.
Andreas Rossberg
@AndreasRossberg Nesta implementação eu adicionei hardcoded id, mas isso deve ser exclusivo usando algo Math.random e Date.now (), etc. E adicionando este id dinâmico, o primeiro ponto pode ser resolvido. Você poderia me fornecer uma solução para os dois últimos pontos.
Ravi Sevta
O primeiro problema é resolvido de forma mais elegante, usando símbolos. Os dois últimos não podem ser resolvidos no JS, por isso o WeakMap precisa ser um primitivo na linguagem.
Andreas Rossberg
1

WeakMap as chaves devem ser objetos, não valores primitivos.

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // works fine (object key)

// can't use a string as the key
weakMap.set("test", "Not ok"); // Error, because "test" is not an object

Por quê????

Vamos ver o exemplo abaixo.

let user = { name: "User" };

let map = new Map();
map.set(user, "...");

user = null; // overwrite the reference

// 'user' is stored inside the map,
// We can get it by using map.keys()

Se usarmos um objeto como a chave em um regular Map, então, embora Mapexista, esse objeto também existe. Ele ocupa memória e não pode ser coletado como lixo.

WeakMapé fundamentalmente diferente neste aspecto. Não impede a coleta de lixo de objetos-chave.

let user = { name: "User" };

let weakMap = new WeakMap();
weakMap.set(user, "...");

user = null; // overwrite the reference

// 'user' is removed from memory!

se usarmos um objeto como a chave nele, e não houver outras referências a esse objeto - ele será removido da memória (e do mapa) automaticamente.

WeakMap não suporta iteração e métodos keys () , values ​​() , entradas () , portanto, não há como obter todas as chaves ou valores dele.

O WeakMap possui apenas os seguintes métodos:

  • fracaMap.get (chave)
  • fracaMap.set (chave, valor)
  • fracaMap.delete (chave)
  • fracoMap.has (chave)

Isso é óbvio, como se um objeto tivesse perdido todas as outras referências (como 'usuário' no código acima), então ele deve ser coletado como lixo automaticamente. Mas tecnicamente não é exatamente especificado quando a limpeza acontece.

O mecanismo JavaScript decide isso. Ele pode optar por realizar a limpeza da memória imediatamente ou aguardar e fazer a limpeza mais tarde, quando ocorrerem mais exclusões. Portanto, tecnicamente, a contagem atual de elementos de a WeakMapnão é conhecida. O motor pode ter limpado ou não ou feito parcialmente. Por esse motivo, os métodos que acessam todas as chaves / valores não são suportados.

Nota: - A principal área de aplicação do WeakMap é um armazenamento de dados adicional. Como armazenar um objeto em cache até que o lixo seja coletado.

Pravin Divraniya
fonte