ES6 Map和WeakMap有什么区别?


92

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


影响在于GC。WeakMaps可以收集其密钥。
John Dvorak

@JanDvorak没有关于MDN的示例。就像aWeakMap.get(key); //说2 ...(GC动作)... aWeakMap.get(key); //说不明
Dmitrii Sorin

1
您的例子是不可能的。key无法收集,因为它已被您引用。
John Dvorak 2013年

1
设计决策是GC动作在Javascript中不可见。您无法观察到GC在做它的事情。
John Dvorak

1
有关此问题的更多信息,请参见此相关答案
本杰明·格林鲍姆

Answers:


53

同一页面上的“ 为什么选择地图? ”部分

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

由于引用弱,因此WeakMap密钥不可枚举(即,没有方法可以为您提供密钥列表)。如果是这样,则该列表将取决于垃圾收集的状态,从而引入不确定性。

[这就是为什么他们也没有size财产的原因]

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

-这将是“正常” Map。在MDN中没有提到,但是在协调提议中,那些也有itemskeys以及values生成器方法和实现Iterator接口


因此new Map().get(x)具有与从普通对象读取属性大约相同的查找时间?
亚历山大·米尔斯

1
@AlexanderMills我不知道这与问题有什么关系,但是这里有一些数据。通常,是的,它们是相似的您应该使用适当的
Bergi '18年

因此,我的理解是Map维护了一个内部数组,因为该数组可以保留其键。垃圾收集器无法取消引用。在WeekMap中,它没有保留键的数组,因此可以对无引用的键进行垃圾回收。
Mohan Ram

@MohanRam A WeakMap仍然具有条目数组(或其他集合),它只是告诉垃圾回收器这些是弱引用
Bergi

那么为什么不支持WeekMap键的迭代?
Mohan Ram '18

92

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

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/


12
为什么不将其从内存中删除?因为您仍然可以参考它!map.entries().next().value // [{x:12}, 1]
Bergi

4
这不是一个自调用函数。这是一个立即调用的函数表达式。benalman.com/news/2010/11/…–
Olson.dev

那么弱映射和对象之间有什么区别
穆罕默德·乌默尔

@MuhammadUmer:对象只能具有字符串“键”,而WeakMap只能具有非原始键(不能将字符串或数字或Symbols作为键,只能使用数组,对象,其他映射等)。
艾哈迈德·法西

1
@nnnnnn是的,这是有区别的,它仍然在Map 但不在WeakMap
Alexander Derck

75

也许对某人来说,下一个解释会更清楚。

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从内存中删除密钥后,我们仍然可以在地图中访问它。同时k2,WeakMap的删除键wm也会通过引用将其从中删除。

这就是为什么WeakMap没有像forEach这样的枚举方法的原因,因为没有WeakMap键的列表之类的东西,它们只是对另一个对象的引用。


10
当然,在最后一行中,wm.get(null)将是未定义的。
DaNeSh '16

8
比从mozilla网站复制和粘贴更好的答案是,荣誉。
乔尔·埃尔南德斯

2
中的forEach(key, val)实际上应该是(val, key)
Miguel Mota'1

令人难以置信的是,一个毫无意义的例子如何获得如此多的赞誉
alfredopacino

34

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

WeakMap的键仅属于Object类型。不允许将原始数据类型用作键(例如,符号不能是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

6
万一有人怀疑:我可以想象这背后的原因是:您不能保留或传递对原始类型的引用。因此,WeakMap中的关键永远是唯一的参考。这样就不可能进行垃圾收集。我不知道弱引用是不可能的还是没有意义。但是,无论哪种方式,密钥都需要被弱引用。
Andreas Linnert

3

来自Javascript.info

地图 -如果我们在常规地图中使用对象作为键,则当地图存在时,该对象也将存在。它占用内存,可能不会被垃圾回收。

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!

3

JavaScript中的WeapMap不保存任何键或值,它只是使用唯一的ID来操纵键值,并为键对象定义一个属性。

因为keykey object通过method 定义属性Object.definePropert(),所以key一定不能是基本类型

并且因为WeapMap实际上不包含键值对,所以我们无法获得weakmap的length属性。

并且将操纵值分配回键对象,如果不使用垃圾回收器,则可以轻松地收集键。

实现示例代码。

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在此实现中,我添加了hardcoded id,但这应该通过使用Math.random和Date.now()等来唯一。通过添加此动态id,可以解决第一点。您能否为我提供最后两点的解决方案。
拉维·塞夫塔

通过使用符号可以更好地解决第一个问题。后两个不能在JS中解决,这就是为什么WeakMap必须是该语言中的原始语言的原因。
Andreas Rossberg

1

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

如果我们将一个对象用作Regular中的键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()entry(),因此无法从中获取所有键或值。

WeakMap仅具有以下方法:

  • weakMap.get(key)
  • weakMap.set(键,值)
  • weakMap.delete(key)
  • weakMap.has(key)

这很明显,就好像一个对象丢失了所有其他引用(例如上面的代码中的“ user”)一样,那么该对象将被自动垃圾回收。但是从技术上讲,清理时并没有确切指定。

JavaScript引擎决定了这一点。当发生更多删除操作时,它可以选择立即执行内存清理,或者等待并稍后进行清理。因此,技术上a的当前元素计数WeakMap未知。引擎可能已经清理过或没有清理过或部分清理过。因此,不支持访问所有键/值的方法。

注意: -WeakMap的主要应用领域是额外的数据存储。就像将对象缓存到该对象被垃圾回收一样。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.