WeakSet
weakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。
首先,WeakSet的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError:Invalid value used in weak set
ws.add(Symbol())
// TypeError:invalid value used in weak set
当试图向Weakset添加一个数值和Symbol值时,会报错,因为WeakSet只能放置对象。
其次,WeakSet中的对象都是弱引用,即垃圾机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不在引用该对象,那么垃圾回收机制会自动回收改对象所占用的内存,不考虑该对象话存在于WeakSet之中。
这是因为垃圾回收机制根据对象的可达性来判断回收,如果对象还能被访问,到垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄露。WeakSet里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面的引用就会自动消失。
由于上面这个特点,WeakSet的成员是不适合引用的,因为它会随时消失。另外由于WeakSet内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此ES6规定WeakSet不可遍历。
WeakMap也具有以上特点。
语法
WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构。
const ws = new WeakSet();
作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数,(实际上,任何具有Interable接口的对象,都可以作为WeakSet的参数。)该数组的所有成员,都会自动成为WeakSet实例对象的成员。
const a = [[2,3],[3,4]];
const ws = new WeakSet(a);
// WeakSet{[1,2],[3,4]}
上面的代码中,a是一个数组,它有两个成员,都是数组。将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。
注意,是a数组的成员成为WeakSet的成员,而不是a数组本身。这意味着,数组的成员只能是对象。
const b = [3,4];
const ws = new WeakSet(b);
// Uncaught TypeError:Invalid value used in weak set(...)
上面的代码中,数组b的成员不是对象,加入WeakSet就会报错。
WeakSet有以下三个方法。
WeakSet.prototype.add(value):添加成员;
WeakSet.prototype.delete(value):清除实例的制定成员;
WeakSet.prottype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); //true
ws.has(foo); //false
ws.delete(window);
ws.has(window); // false
WeakSet没有size属性和forEach属性,因为WeakSet不能遍历,是因为成员都是弱引用,随时可能取消,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档中移除,会随时引发内存泄露。
下面是WeakSet的另一个例子。
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
id(!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
上面的代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。