当与它们的键/值引用的对象被删除时,它们会表现出不同的行为。让我们看下面的示例代码:
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}
。map.entries().next().value // [{x:12}, 1]
- BergiWeakMap
只能有非原始键(没有字符串或数字或Symbol
作为键,只有数组、对象、其他映射等)。 - Ahmed Fasih{x: 12}
和{y: 12}
了。" - 但正如你所解释的那样,{x: 12}
仍然在Map
中。 - nnnnnnMap
中但不在 WeakMap
中。 - Alexander Derck也许下一个解释会更容易理解。
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键的列表,它们只是对其他对象的引用。
forEach
中,(key, val)
应该实际上是(val, key)
。 - Miguel Mota有经验的JavaScript程序员会注意到,这个API可以用两个数组(一个用于键,一个用于值)在4个API方法之间共享来在JavaScript中实现。这样的实现将有两个主要的不便。第一个是O(n)搜索(n是映射中键的数量)。第二个是内存泄漏问题。对于手动编写的映射,键数组将保留对键对象的引用,阻止它们被垃圾回收。在本机WeakMaps中,对键对象的引用被“弱”地保持,这意味着如果没有其他对该对象的引用,则不会防止垃圾回收。
由于引用是弱的,WeakMap键不可枚举(即没有给出键列表的方法)。如果有,该列表将取决于垃圾回收的状态,引入了不确定性。
如果您想要一个键列表,您应该自行维护。还有一个ECMAScript提案旨在引入简单的集合和映射,这些集合和映射不使用弱引用且可枚举。
- 这将是“普通”Map
。虽然MDN没有提到,但在harmony提案中,它们也具有items
,keys
和values
生成器方法,并实现Iterator
接口。
[这就是为什么它们也没有size
属性的原因]
new Map().get(x)
的查找时间与从普通对象中读取属性的时间大致相同? - Alexander Mills另一个区别(来源: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
来自 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!
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
。引擎可能已经清理了它,也可能没有清理或部分清理。出于这个原因,不支持访问所有键/值的方法。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;
})();
实现的参考链接
id
,但是应该使用一些 Math.random 和 Date.now() 等方法使其唯一。
通过添加这个动态 id,第一个问题可以解决。您能否为最后两个问题提供解决方案? - Ravi Sevta
key
不能被收集,因为它被你引用了。 - John Dvorak