JavaScript高级程序设计(第4版)
上QQ阅读APP看书,第一时间看更新

6.7 WeakSet

ECMAScript 6新增的“弱集合”(WeakSet)是一种新的集合类型,为这门语言带来了集合数据结构。WeakSet是Set的“兄弟”类型,其API也是Set的子集。WeakSet中的“weak”(弱),描述的是JavaScript垃圾回收程序对待“弱集合”中值的方式。

6.7.1 基本API

可以使用new关键字实例化一个空的WeakSet:

    const ws = new WeakSet();

弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError。

如果想在初始化时填充弱集合,则构造函数可以接收一个可迭代对象,其中需要包含有效的值。可迭代对象中的每个值都会按照迭代顺序插入到新实例中:

    const val1 = {id: 1},
          val2 = {id: 2},
          val3 = {id: 3};
    // 使用数组初始化弱集合
    const ws1 = new WeakSet([val1, val2, val3]);
    alert(ws1.has(val1)); // true
    alert(ws1.has(val2)); // true
    alert(ws1.has(val3)); // true
    // 初始化是全有或全无的操作
    // 只要有一个值无效就会抛出错误,导致整个初始化失败
    const ws2 = new WeakSet([val1, "BADVAL", val3]);
    // TypeError: Invalid value used in WeakSet
    typeof ws2;
    // ReferenceError: ws2 is not defined
    // 原始值可以先包装成对象再用作值
    const stringVal = new String("val1");
    const ws3 = new WeakSet([stringVal]);
    alert(ws3.has(stringVal)); // true

初始化之后可以使用add()再添加新值,可以使用has()查询,还可以使用delete()删除:

    const ws = new WeakSet();
    const val1 = {id: 1},
          val2 = {id: 2};
    alert(ws.has(val1)); // false
    ws.add(val1)
      .add(val2);
    alert(ws.has(val1)); // true
    alert(ws.has(val2)); // true
    ws.delete(val1);      // 只删除这一个值
    alert(ws.has(val1)); // false
    alert(ws.has(val2)); // true

add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明:

    const val1 = {id: 1},
          val2 = {id: 2},
          val3 = {id: 3};
    const ws = new WeakSet().add(val1);
    ws.add(val2)
      .add(val3);
    alert(ws.has(val1)); // true
    alert(ws.has(val2)); // true
    alert(ws.has(val3)); // true

6.7.2 弱值

WeakSet中“weak”表示弱集合的值是“弱弱地拿着”的。意思就是,这些值不属于正式的引用,不会阻止垃圾回收。

来看下面的例子:

    const ws = new WeakSet();
    ws.add({});

add()方法初始化了一个新对象,并将它用作一个值。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收。然后,这个值就从弱集合中消失了,使其成为一个空集合。

再看一个稍微不同的例子:

    const ws = new WeakSet();
    const container = {
      val: {}
    };
    ws.add(container.val);
    function removeReference() {
      container.val = null;
    }

这一次,container对象维护着一个对弱集合值的引用,因此这个对象值不会成为垃圾回收的目标。不过,如果调用了removeReference(),就会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清理掉。

6.7.3 不可迭代值

因为WeakSet中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力。当然,也用不着像clear()这样一次性销毁所有值的方法。WeakSet确实没有这个方法。因为不可能迭代,所以也不可能在不知道对象引用的情况下从弱集合中取得值。即便代码可以访问WeakSet实例,也没办法看到其中的内容。

WeakSet之所以限制只能用对象作为值,是为了保证只有通过值对象的引用才能取得值。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

6.7.4 使用弱集合

相比于WeakMap实例,WeakSet实例的用处没有那么大。不过,弱集合在给对象打标签时还是有价值的。

来看下面的例子,这里使用了一个普通Set:

    const disabledElements = new Set();
    const loginButton = document.querySelector('#login');
    // 通过加入对应集合,给这个节点打上“禁用”标签
    disabledElements.add(loginButton);

这样,通过查询元素在不在disabledElements中,就可以知道它是不是被禁用了。不过,假如元素从DOM树中被删除了,它的引用却仍然保存在Set中,因此垃圾回收程序也不能回收它。

为了让垃圾回收程序回收元素的内存,可以在这里使用WeakSet:

    const disabledElements = new WeakSet();
    const loginButton = document.querySelector('#login');
    // 通过加入对应集合,给这个节点打上“禁用”标签
    disabledElements.add(loginButton);

这样,只要WeakSet中任何元素从DOM树中被删除,垃圾回收程序就可以忽略其存在,而立即释放其内存(假设没有其他地方引用这个对象)。