ES6 Map和WeakMap有什么区别?

115

查看thisthis的MDN页面,似乎Maps和WeakMaps之间唯一的区别是WeakMaps缺少“size”属性。但这是真的吗?它们之间有什么区别?


影响在于垃圾回收器。WeakMaps可以使它们的键被收集。 - John Dvorak
@JanDvorak 在 MDN 上没有关于它的示例。比如 aWeakMap.get(key); // 假设为 2 ...(GC 操作)... aWeakMap.get(key); // 假设为 undefined - Dmitrii Sorin
1
你的例子是不可能的。key不能被收集,因为它被你引用了。 - John Dvorak
1
设计决策是GC操作在Javascript中是不可见的。您无法观察到GC正在执行其操作。 - John Dvorak
1
有关此问题的更多信息,请参见此相关答案 - Benjamin Gruenbaum
显示剩余3条评论
7个回答

123

当与它们的键/值引用的对象被删除时,它们会表现出不同的行为。让我们看下面的示例代码:

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);
})()
当执行以上IIFE后,我们再也无法引用{x:12}{y:12}了。垃圾回收器将删除“WeakMap”中键b的指针,并从内存中删除{y:12}。但在“Map”中,垃圾回收器不会删除“Map”中的指针,也不会从内存中删除{x:12}
总结:WeakMap可以让垃圾回收器完成其任务,但Map不能。
参考资料:http://qnimate.com/difference-between-map-and-weakmap-in-javascript/

19
为什么它没有从内存中删除?因为您仍然可以引用它!map.entries().next().value // [{x:12}, 1] - Bergi
7
这不是一个自调用函数,而是一个立即调用的函数表达式。http://benalman.com/news/2010/11/immediately-invoked-function-expression/ - Olson.dev
1
@MuhammadUmer:对象只能有字符串“键”,而WeakMap只能有非原始键(没有字符串或数字或Symbol作为键,只有数组、对象、其他映射等)。 - Ahmed Fasih
"我们再也无法引用{x: 12}{y: 12}了。" - 但正如你所解释的那样,{x: 12}仍然在Map中。 - nnnnnn
1
@nnnnnn 是的,这就是区别,它仍然在 Map 中但不在 WeakMap 中。 - Alexander Derck
显示剩余2条评论

94

也许下一个解释会更容易理解。

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

如您所见,从内存中删除k1键后,我们仍然可以在map内部访问它。同时,从WeakMap中删除k2键也将通过引用将其从wm中删除。

这就是为什么WeakMap没有像forEach这样的可枚举方法的原因,因为不存在WeakMap键的列表,它们只是对其他对象的引用。


14
当然,在最后一行中,wm.get(null)将是未定义的。 - DaNeSh
9
比起从Mozilla网站复制粘贴的回答,这个回答更好,值得赞扬。 - Joel Hernandez
3
forEach中,(key, val)应该实际上是(val, key) - Miguel Mota
3
难以置信一个毫无意义的例子能够获得如此多的赞同。 - alfredopacino
1
map.get(k1) 也会返回 null。 - KJ Sudarshan
这根本不是公平的比较。 - Kritish Bhattarai

57

来自同一页面,章节“为什么使用Weak Map?”

有经验的JavaScript程序员会注意到,这个API可以用两个数组(一个用于键,一个用于值)在4个API方法之间共享来在JavaScript中实现。这样的实现将有两个主要的不便。第一个是O(n)搜索(n是映射中键的数量)。第二个是内存泄漏问题。对于手动编写的映射,键数组将保留对键对象的引用,阻止它们被垃圾回收。在本机WeakMaps中,对键对象的引用被“弱”地保持,这意味着如果没有其他对该对象的引用,则不会防止垃圾回收。

由于引用是弱的,WeakMap键不可枚举(即没有给出键列表的方法)。如果有,该列表将取决于垃圾回收的状态,引入了不确定性。

如果您想要一个键列表,您应该自行维护。还有一个ECMAScript提案旨在引入简单的集合和映射,这些集合和映射不使用弱引用且可枚举。

- 这将是“普通”Map。虽然MDN没有提到,但在harmony提案中,它们也具有itemskeysvalues生成器方法,并实现Iterator接口

[这就是为什么它们也没有size属性的原因]


那么 new Map().get(x) 的查找时间与从普通对象中读取属性的时间大致相同? - Alexander Mills
1
@AlexanderMills 我不明白这与问题有什么关系,但是这里有一些数据。一般来说,它们是相似的,并且你应该使用适当的一个 - Bergi
我的理解是,Map维护一个内部数组来保存其键,因此垃圾收集器无法阻止引用。在WeakMap中,它没有维护键的数组,因此没有引用的键可以被垃圾回收。 - Mohan Ram
@MohanRam WeakMap 仍然具有条目的数组(或其他集合),它只是告诉垃圾收集器这些是弱引用 - Bergi
1
@MohanRam请查看答案中第一个引用的第二段。 - Bergi
显示剩余2条评论

38

另一个区别(来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap):

WeakMap 的键类型仅限于对象。不允许使用原始数据类型作为键(例如,符号无法成为 WeakMap 的键)。

字符串、数字或布尔值也不能用作 WeakMap 的键。但是,Map 可以使用原始数据类型作为键。

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

m = new Map
m.set('a', 'b'); // Works

7
如果有人想知道原因,我能想象这背后的原因是:你不能保留或传递基本类型的引用。所以 WeakMap 中的键只有一个引用,那就是它自己。这样就不可能进行垃圾回收了。我不知道弱引用是不可能还是没有意义。但无论如何,键都需要是可以被弱引用的东西。 - Andreas Linnert

14

来自 Javascript.info

Map -- 如果我们在普通的 Map 中使用对象作为键,则在 Map 存在时,该对象也存在。它会占用内存,并且可能无法进行垃圾回收。

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]

类似于这样,如果我们在常规的Map中使用对象作为键,则在Map存在时,该对象也存在。它占用内存并且可能不会被垃圾回收。

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 -- 如果我们将一个对象用作其键,并且没有其他对该对象的引用 - 它将自动从内存(和映射中)中删除。

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

// john is removed from memory!

6

WeakMap 的键必须是对象,而不能是原始值。

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

为什么?

让我们看下面的例子。

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

如果我们在常规的Map中使用对象作为键,那么只要该Map存在,该对象也会存在。它会占用内存且可能不会被垃圾回收。
从这个方面来看,WeakMap有根本的不同。它不会阻止键对象的垃圾回收。
let user = { name: "User" };

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

user = null; // overwrite the reference

// 'user' is removed from memory!

如果我们在一个对象中使用它作为键,并且该对象没有其他引用 - 它将自动从内存(和映射)中删除。 WeakMap不支持迭代和方法keys(), values(), entries(),因此无法从中获取所有键或值。 WeakMap只有以下方法:
- weakMap.get(key) - weakMap.set(key, value) - weakMap.delete(key) - weakMap.has(key)
这很明显,因为如果一个对象失去了所有其他引用(如上面的“user”),则它将自动进行垃圾回收。但从技术上讲,清理发生的时间并没有精确指定。
JavaScript引擎会决定。它可以选择立即执行内存清理,也可以等待并在更多删除发生时稍后进行清理。因此,技术上不知道当前元素计数的WeakMap。引擎可能已经清理了它,也可能没有清理或部分清理。出于这个原因,不支持访问所有键/值的方法。
请注意:WeakMap的主要应用领域是附加数据存储。例如缓存对象,直到该对象被垃圾回收。

2

JavaScript中的WeakMap不保存任何键或值,它只是使用唯一标识符来操作键值,并为键对象定义属性。

由于它使用Object.definePropert()方法为键对象定义属性,因此键必须不是原始类型

并且由于WeakMap实际上不包含键值对,因此我们无法获取WeakMap的长度属性。

另外,操作后的值被分配回键对象,如果键对象没有用途,垃圾收集器可以轻松地收集该键。

以下是实现的示例代码。

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

实现的参考链接


1
要明确的是,这个实现只能半途而废。它不允许在多个弱映射中使用同一个对象作为键。它也不能用于冻结对象。当然,它会向任何持有对象引用的人泄露映射信息。第一个问题可以使用符号来解决,但后两个则无法解决。 - Andreas Rossberg
@AndreasRossberg 在这个实现中,我已经添加了硬编码的 id,但是应该使用一些 Math.random 和 Date.now() 等方法使其唯一。 通过添加这个动态 id,第一个问题可以解决。您能否为最后两个问题提供解决方案? - Ravi Sevta
第一个问题可以通过使用符号更优雅地解决。后两个问题无法在JS中解决,这就是为什么WeakMap必须成为语言中的原语。 - Andreas Rossberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接