const keyObject = ['keyObject'];
new WeakMap().set(keyObject, ['value']);
问题:现在 ['value'] 会被释放吗?
听说WeakMap是o(1)复杂度的,而且不会存在内存泄漏问题,那么就只有一种实现机制,就是value直接通过一个隐形键挂在keyObject上。
但如果是这样,而WeakMap本身又没有引用它之前添加过那些内容,那么是不是如果keyObject不释放,即便WeakMap实例释放了,通过该WeakMap实例添加在keyObject上的value是不是也都不会释放,从而形成另一种内存泄漏?
jsperf.com只能测试性能,不知道内存泄漏该如何测试?
答案:会正确被释放
测试过程:
[1]Chrome DevTools 控制台上有一个小众的 API 叫
queryObjects()
,它可以从原型树上反查所有直接或间接的继承了某个对象的其它对象,比如
queryObjects(Array.prototype)
可以拿到所有的数组对象,
queryObjects(Object.prototype)
则基本上可以拿到页面里的所有对象了(除了继承自Object.create(null)的对象之外)。而且关键是这个 API 会在内存里搜索对象前先进行一次垃圾回收。
【测试1】
const key = new WeakMap();
const map = new WeakMap();
map.set(key, new WeakMap());
undefined;
在chrome控制台运行
查到了 3 个对象,符合预期
【测试2】
const key = new WeakMap();
new WeakMap().set(key, new WeakMap());
undefined;
在chrome控制台运行
只有一个WeakMap没有被回收
那么WeakMap是怎么做到的呢?
核心在于WeakMap上的kv对是弱引用的
let wm = new WeakMap([[k1, v1], [k2, v2]]) // vm = {k1:v1, k2:v2}
wm.size // no such property
wm.keys(); // no such function
wm.forEach(...) // unable to be iterated
WeakMap有2个特点
- 属性不可枚举
- key必须是Object类型
看一下WeakMap的polyfill
var WeakMap = function() {
this.name = '__wm__' + uuid()
};
WeakMap.prototype = {
set: function(key, value) {
Object.defineProperty(key, this.name, {
value: [key, value],
});
return this;
},
get: function(key) {
var entry = key[this.name];
return entry && (entry[0] === key ? entry[1] : undefined);
},
...
};
- weakmap.set(key, val)事实上是直接通过Object.defineProperty给这个key加了一个新属性
—— WeakMap的key必须是Object类型的原因
- 相比Map,WeakMap持有的只是每个键值对的“弱引用”,不会额外开内存保存键值引用。这意味着在没有其他引用存在时,垃圾回收器能正确处理key指向的内存块。
—— WeakMap的key不可枚举的原因
延伸阅读
1.Object.defineProperty(obj, "prop", propDesc)和obj.prop = value的区别?
2.什么是弱引用?
WeakMap不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。
参考资料