WeakMap结构与Map结构类似,也用于生成键值对的集合.

含义

WeakMap只接受对象作为键名(null)除外

const map = new WeakMap();
map.set(1, 2);
// TypeError: 1 is not an object!

WeakMap的键名所指向的对象不计入垃圾回收机制

const e1 = doucment.getElementById('foo');
const e2 = doucment.getElementById('bar');
const arr = [
 [e1, 'foo元素'],
 [e2, 'bar元素'],
];

  上面的代码中, e1和e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明,这就形成了arr对e1和e2的引用.

  一旦不再需要这两个对象,我们必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存.

// 不需要e1和e2的时候
// 必须手动删除引用
arr[0] = null;
arr[1] = null;

  上面的这样的写法显然很不方便,一旦忘了写,就会造成内存泄漏.

  WeakMap就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内. 因此,只要所引用的对象的其他引用都被清楚,垃圾回收机制会释放该对象所占用的内存,也就是说,一旦不再需要,WeakMap里面的键名对象和所对应的键值对会自动消失,不用手动删除引用.

语法

WeakMap与Map在API上的区别主要有两个.

  1. 没有遍历操作(即没有key(), values()和entries()方法),也没有size属性.因为没有办法列出所有键名,某个键名是否存在完全不可预测,和垃圾回收机制是否运行相关.这一刻可以取到键名,下一刻垃圾回收机制突然运行,这个键名就消失了,为了防止出现不确定性,因此统一规定不能取到键名.
  2. 无法清空,即不支持clear方法,因此WeakMap只有4个方法可用:get()、set()、has()、delete()。

用途

WeakMap应用的典型场景就是以DOM节点作为键名的场景。

注册监听事件的listener对象

注册监听事件的listener对象很适合用WeakMap来实现.

const listener = new WeakMap();
listener.set(element1, handler1);
listener.set(element2, handler2);

element1.addEventlistener('click', listener.get(element1), false);
element2.addEventlistener('click', listener.get(element2), false);

上面的代码中,监听函数放在WeakMap里面.一旦DOM对象消失,与它绑定的监听函数也会自动消失.

部署私有属性

WeakMap的另一个用处是部署私有属性。

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

const c = new Countdown(2, () => console.log('DONE'));
c.dec();
c.dec();
// DONE

上面的代码中,Countdown类的两个内部属性 ---_counter 和 _action---是实例的弱引用,如果删除实例,他们也会随之消失,不会造成内存泄漏.