¿Cuál es la diferencia entre ES6 Map y WeakMap?

93

Mirando esta y esta página MDN, parece que la única diferencia entre Maps y WeakMaps es una propiedad de "tamaño" que falta para WeakMaps. ¿Pero es esto cierto? ¿Cual es la diferencia entre ellos?

Dmitrii Sorin
fuente
El efecto está en el GC. WeakMaps puede recopilar sus claves.
John Dvorak
@JanDvorak no hay ningún ejemplo señalado en MDN al respecto. Como aWeakMap.get (clave); // digamos, 2 ... (acción GC) ... aWeakMap.get (clave); // decir, indefinido
Dmitrii Sorin
1
Tu ejemplo es imposible. keyno se puede recopilar porque usted hace referencia a él.
John Dvorak
1
La decisión de diseño es que las acciones de GC son invisibles en Javascript. No se puede observar a GC haciendo lo suyo.
John Dvorak
1
Consulte esta respuesta relacionada para obtener más información sobre este problema.
Benjamin Gruenbaum

Respuestas:

53

De la misma página, sección " ¿Por qué Weak Map? " :

El programador experimentado de JavaScript notará que esta API podría implementarse en JavaScript con dos matrices (una para claves y otra para valores) compartidas por los 4 métodos de API. Tal implementación tendría dos inconvenientes principales. La primera es una búsqueda O (n) (siendo n el número de claves en el mapa). El segundo es un problema de pérdida de memoria. Con mapas escritos manualmente, la matriz de claves mantendría referencias a objetos clave, evitando que sean recolectados como basura. En WeakMaps nativos, las referencias a objetos clave se mantienen "débilmente" , lo que significa que no impiden la recolección de basura en caso de que no haya otra referencia al objeto.

Debido a que las referencias son débiles, las claves de WeakMap no se pueden enumerar (es decir, no hay ningún método que le proporcione una lista de las claves). Si lo fueran, la lista dependería del estado de la recolección de basura, introduciendo el no determinismo.

[Y es por eso que tampoco tienen sizepropiedad]

Si desea tener una lista de claves, debe mantenerla usted mismo. También hay una propuesta de ECMAScript que apunta a introducir conjuntos y mapas simples que no usarían referencias débiles y serían enumerables.

- que serían los "normales" Maps . No se menciona en el MDN, pero en la propuesta de la armonía , los que también tienen items, keysy valuesmétodos de generador y poner en práctica la Iteratorinterfaz .

Bergi
fuente
¿ new Map().get(x)Tiene aproximadamente el mismo tiempo de búsqueda que leer una propiedad de un objeto simple?
Alexander Mills
1
@AlexanderMills No veo qué tiene esto que ver con la pregunta, pero aquí hay algunos datos . En general, sí son similares , y debes usar el adecuado .
Bergi
Entonces, según tengo entendido, Map mantiene una matriz interna para conservar su clave debido a esa matriz. El recolector de basura no puede evitar la referencia. En WeekMap, no tiene una matriz donde se mantengan las claves, por lo que la clave sin referencia se puede recolectar como basura.
Mohan Ram
@MohanRam A WeakMaptodavía tiene una matriz (u otra colección) de entradas, solo le dice al recolector de basura que esas son referencias débiles .
Bergi
Entonces, ¿por qué no se admite la iteración de claves WeekMap?
Mohan Ram
92

Ambos se comportan de manera diferente cuando se elimina un objeto al que hacen referencia sus claves / valores. Tomemos el siguiente código de ejemplo:

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

El IIFE anterior se ejecuta, no hay forma de que podamos hacer referencia {x: 12}y {y: 12}más. El recolector de basura sigue adelante y elimina el puntero clave b de "WeakMap" y también lo elimina {y: 12}de la memoria. Pero en el caso de "Mapa", el recolector de basura no elimina un puntero de "Mapa" y tampoco lo elimina {x: 12}de la memoria.

Resumen: WeakMap permite que el recolector de basura haga su tarea pero no Map.

Referencias: http://qnimate.com/difference-between-map-and-weakmap-in-javascript/

kshirish
fuente
12
¿Por qué no se elimina de la memoria? ¡Porque todavía puedes hacer referencia a él! map.entries().next().value // [{x:12}, 1]
Bergi
4
No es una función autoinvocada. Es una expresión de función invocada inmediatamente. benalman.com/news/2010/11/…
Olson.dev
entonces, ¿cuál es la diferencia entre mapa débil y objeto?
Muhammad Umer
@MuhammadUmer: el objeto solo puede tener 'claves' de cadena, mientras WeakMapque solo puede tener claves no primitivas (sin cadenas o números o Symbols como claves, solo matrices, objetos, otros mapas, etc.).
Ahmed Fasih
1
@nnnnnn Sí, esa es la diferencia, todavía está en el Map pero no en elWeakMap
Alexander Derck
75

Quizás la siguiente explicación sea más clara para alguien.

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 ves, después de quitar la k1llave de la memoria todavía podemos acceder a ella dentro del mapa. Al mismo tiempo, al eliminar la k2clave de WeakMap, también se elimina wmpor referencia.

Es por eso que WeakMap no tiene métodos enumerables como forEach, porque no existe una lista de claves WeakMap, son solo referencias a otros objetos.

Rax Wunter
fuente
10
en la última línea, por supuesto, wm.get (null) no estará definido.
DaNeSh
8
Mejor respuesta que copiar y pegar desde el sitio de mozilla, felicitaciones.
Joel Hernandez
2
en el forEach, (key, val)debería ser en realidad(val, key)
Miguel Mota
Increíble cómo un ejemplo que no tiene sentido obtiene tantos votos a favor
Alfredopacino
34

Otra diferencia (fuente: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap ):

Las claves de WeakMaps son solo del tipo Objeto. No se permiten tipos de datos primitivos como claves (por ejemplo, un símbolo no puede ser una clave WeakMap).

Tampoco se puede usar una cadena, un número o un valor booleano como WeakMapclave. A Map puede usar valores primitivos para claves.

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
fuente
6
En caso de que alguien se pregunte: me imagino que la razón detrás de esto es: no se pueden mantener o pasar referencias a tipos primitivos. Entonces, la clave en un WeakMap sería su única referencia. De esa forma, la recolección de basura no sería posible. No sé si las referencias débiles son imposibles o simplemente no tienen sentido. Pero de cualquier forma, la clave debe ser algo a lo que se pueda hacer referencia débilmente.
Andreas Linnert
3

De Javascript.info

Mapa : si usamos un objeto como clave en un mapa normal, mientras el mapa existe, ese objeto también existe. Ocupa memoria y no se puede recolectar basura.

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]

De manera similar, si usamos un objeto como clave en un mapa normal, mientras el mapa existe, ese objeto también existe. Ocupa memoria y no se puede recolectar basura

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 : ahora, si usamos un objeto como clave y no hay otras referencias a ese objeto, se eliminará de la memoria (y del mapa) automáticamente.

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

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

WeapMap en javascript no contiene claves ni valores, solo manipula el valor de la clave usando una identificación única y define una propiedad para el objeto clave.

debido a que define la propiedad key objectpor método Object.definePropert(), la clave no debe ser de tipo primitivo .

y también debido a que WeapMap no contiene pares de valores clave, no podemos obtener la propiedad de longitud del mapa débil.

y también el valor manipulado se asigna de nuevo al objeto clave, el recolector de basura puede recolectar la clave fácilmente si no se usa.

Código de muestra para implementación.

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

referencia de implementación

Ravi Sevta
fuente
1
Para ser claros, esta implementación solo funciona a medias. No permitirá usar el mismo objeto como clave en múltiples mapas débiles. Tampoco funciona para objetos congelados. Y, por supuesto, filtra el mapeo a cualquiera que tenga una referencia al objeto. El primero se puede arreglar mediante símbolos, pero no los dos últimos.
Andreas Rossberg
@AndreasRossberg En esta implementación agregué código id, pero esto debería ser único usando algo Math.random y Date.now (), etc. Y agregando esta identificación dinámica, el primer punto puede resolverse. ¿Podría darme una solución para los dos últimos puntos?
Ravi Sevta
El primer problema se resuelve de forma más elegante mediante el uso de símbolos. Los dos últimos no se pueden resolver dentro de JS, por lo que WeakMap tiene que ser un primitivo en el lenguaje.
Andreas Rossberg
1

WeakMap las claves deben ser objetos, no 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é????

Veamos el siguiente ejemplo.

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

Si usamos un objeto como clave en un programa regular Map, mientras Mapexista, ese objeto también existe. Ocupa memoria y no se puede recolectar basura.

WeakMapes fundamentalmente diferente en este aspecto. No evita la recolección de basura de objetos clave.

let user = { name: "User" };

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

user = null; // overwrite the reference

// 'user' is removed from memory!

si usamos un objeto como clave en él y no hay otras referencias a ese objeto, se eliminará de la memoria (y del mapa) automáticamente.

WeakMap no admite iteración y métodos keys () , valores () , entradas () , por lo que no hay forma de obtener todas las claves o valores de él.

WeakMap solo tiene los siguientes métodos:

  • debilMap.get (clave)
  • débilMap.set (clave, valor)
  • debilMap.delete (clave)
  • debilMap.has (clave)

Eso es obvio, como si un objeto hubiera perdido todas las demás referencias (como 'usuario' en el código anterior), entonces debe ser recolectado automáticamente. Pero técnicamente no se especifica exactamente cuándo ocurre la limpieza.

El motor de JavaScript decide eso. Puede optar por realizar la limpieza de la memoria inmediatamente o esperar y hacer la limpieza más tarde cuando ocurran más eliminaciones. Por lo tanto, técnicamente WeakMapno se conoce el recuento actual de elementos de a . El motor puede haberlo limpiado o no o lo hizo parcialmente. Por esa razón, no se admiten los métodos que acceden a todas las claves / valores.

Nota: - El área principal de aplicación de WeakMap es un almacenamiento de datos adicional. Como almacenar en caché un objeto hasta que ese objeto sea recolectado como basura.

Pravin Divraniya
fuente