阅读 632

强引用和弱引用——Set、WeakSet、Map、WeakMap区别

WeakSet中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。 由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。 这些特点同样适用于本章后面要介绍的 WeakMap 结构。-- 阮一峰

Set

类似数组,不过没有key,或者说key和value一样。可以添加、删除、清空、判断是否包含某个value,可以枚举。

将引用型变量key加入set,set对key引用的对象obj(这里的obj就是new Array创建的数组)建立强引用,设置key=null只是取消了key对obj的强引用,而set对obj仍有强引用,gc不会回收obj。除非在设置key=null之前,将key从set中删除,即依次执行set.delete(key); key=null。

const s = new Set();
let key = new Array(5 * 1024 * 1024);
s.add(key);
复制代码

目录结构如下:

image.png

lib.js提供的print做了两件事——执行垃圾回收并打印内存使用信息,其他几个文件相似。

使用 node --expose-gc map.js 进行手动内存回收

// lib.js
 const format = function (bytes) {
    return (bytes / 1024 / 1024).toFixed(2) + ' MB';
};

const print = function() {
    global.gc();
    const memoryUsage = process.memoryUsage();
    console.log(JSON.stringify({
        rss: format(memoryUsage.rss),
        heapTotal: format(memoryUsage.heapTotal),
        heapUsed: format(memoryUsage.heapUsed),
        external: format(memoryUsage.external),
    }));
}

module.exports = {
    format,
    print,
}


// set.js
const lib = require('./lib');
const { print } = lib;

print();
const s = new Set();
let key = new Array(5 * 1024 * 1024);
s.add(key);
print();
key = null;
print();

// set2.js
const lib = require('./lib');
const { print } = lib;

print();
const s = new Set();
let key = new Array(5 * 1024 * 1024);
s.add(key);
print();
s.delete(key);
key = null;
print();
复制代码

WeakSet

Set中对象的引用都是强引化,并不会允许垃圾回收;它能存储任意类型——值类型或引用类型。

WeakSet中对象的引用是弱引用,也就是说,即使weakset“引用”了某个对象,但垃圾回收不把这种引用计为“引用”,只要其他地方没有强引用这个对象,该对象就不可达,任何时刻可能被回收;只能存储引用类型,且不可枚举、不可清除。

// weakset.js
const lib = require('./lib');
const { print } = lib;

print();
const s = new WeakSet();
let key = new Array(5 * 1024 * 1024);
s.add(key);
print();
key = null;
print();
复制代码

image.png

经过对比发现,set.js最后两个print之间,key = null,但是set还是强引用了key引用的对象obj,所以这个对象没有被回收;而set2.js最后两个print之间,set.delete(key)取消了set对key引用对象obj的强引用,随后key=null又取消了key对obj的强引用,此时obj不可达,执行第三次print时被回收;weakset.js中的weakset对key引用的对象obj是弱引用,当key=null时,没有对象引用的obj,obj不可达,最后一次print被回收。

Map

Map是键值对的集合,可以存、取、清空、删除、判断是否包含某个key,可以枚举。map对key所引用的对象是强引用。

WeakMap

WeakMap和Map相似,不过不可枚举、不可清空、对key所引用的对象是弱引用(参考WeakSet那节)。

使用场景:注册监听事件的listener对象

const listener = new WeakMap();
 
listener.set(ele1, handler1);
listener.set(ele2, handler2);

ele1.addEventListener('click', listener.get(ele1), false);
ele2.addEventListener('click', listener.get(ele2), false);
复制代码

由于监听函数是放在 WeakMap 里面,则一旦dom对象ele1,ele2消失,与它绑定的监听函数handler1和handler2 也会自动消失,免除手动解除监听函数

附上Set、Map、WeakSet、WeakMap用法对比

image.png

文章分类
前端
文章标签