JavaScript WeakMap 与 Map 的区别详解
什么是 WeakMap
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。
WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = { foo: 1 };
wm1.set(key, 2);
wm1.get(key); // 2
WeakMap 也可以接受一个数组,作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([
[k1, 'foo'],
[k2, 'bar'],
]);
wm2.get(k2); // "bar"
WeakMap 与 Map 的区别
首先,WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。
const map = new WeakMap();
// TypeError: 1 is not an object!
map.set(1, 2);
// TypeError: Invalid value used as weak map key
map.set(Symbol(), 2);
// TypeError: Invalid value used as weak map key
map.set(null, 2);
上面代码中,如果将数值 1 和 Symbol 值作为 WeakMap 的键名,都会报错。 其次,WeakMap 的键名所指向的对象,不计入垃圾回收机制。
作用
WeakMap 的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。
const e1 = document.getElementById('foo');
const e2 = document.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里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
示例
const wm = new WeakMap();
let key = {};
let obj = 'value';
wm.set(key, obj);
key = null;
console.log(wm);
如上述代码,我们在浏览器上测试一下,我们将 key 这个引用主动设置为 null 时,浏览器的垃圾回收机制发现 WeakMap 的 key 引用消失时,会自动的将此键值对给回收。

tip
WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = { foo: 1 };
wm.set(key, obj);
obj = null;
wm.get(key);
// Object {foo: 1}
上面代码中,键值 obj 是正常引用。所以,即使在 WeakMap 外部消除了 obj 的引用,WeakMap 内部的引用依然存在。
WeakMap 的语法
WeakMap 与 Map 在 API 上的区别主要是两个
- 没有遍历操作(即没有
keys()、values()和entries()方法),也没有size属性。 因为没有办法列出所有键名,某个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。 这一刻可以取到键名,下一刻垃圾回收机制突然运行了,这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。 - 无法清空,即不支持
clear方法。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
const wm = new WeakMap();
// size、forEach、clear 方法都不存在
wm.size; // undefined
wm.forEach; // undefined
wm.clear; // undefined
WeakMap 的用途
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, { timesClicked: 0 });
myElement.addEventListener(
'click',
function () {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
},
false,
);
上面代码中,myElement 是一个 DOM 节点,每当发生 click 事件,就更新一下状态。 我们将这个状态作为键值放在 WeakMap 里,对应的键名就是 myElement。一旦这个 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,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

还没有留言,来发表第一条吧~