WeakMap 实例与现有JavaScript 对象有着很大不同,可能一时不容易说清楚应该怎么使用它。这个
问题没有唯一的答案,但已经出现了很多相关策略。
- 私有变量
弱映射造就了在JavaScript 中实现真正私有变量的一种新方式。前提很明确:私有变量会存储在弱
映射中,以对象实例为键,以私有成员的字典为值。
下面是一个示例实现:
const wm = new WeakMap();
class User {
constructor(id) {
this.idProperty = Symbol(‘id’);
this.setId(id);
}
setPrivate(property, value) {
const privateMembers = wm.get(this) || {};
privateMembers[property] = value;
wm.set(this, privateMembers);
}
getPrivate(property) {
return wm.get(this)[property];
}
setId(id) {
this.setPrivate(this.idProperty, id);
}
getId() {
return this.getPrivate(this.idProperty);
}
}
const user = new User(123);
alert(user.getId()); // 123
user.setId(456);
alert(user.getId()); // 456
// 并不是真正私有的
alert(wm.get(user)[user.idProperty]); // 456
慧眼独具的读者会发现,对于上面的实现,外部代码只需要拿到对象实例的引用和弱映射,就可以
取得“私有”变量了。为了避免这种访问,可以用一个闭包把WeakMap 包装起来,这样就可以把弱映
射与外界完全隔离开了:
const User = (() => {
const wm = new WeakMap();
class User {
constructor(id) {
this.idProperty = Symbol(‘id’);
this.setId(id);
}
setPrivate(property, value) {
const privateMembers = wm.get(this) || {};
privateMembers[property] = value;
wm.set(this, privateMembers);
}
getPrivate(property) {
return wm.get(this)[property];
}
setId(id) {
this.setPrivate(this.idProperty, id);
}
getId(id) {
return this.getPrivate(this.idProperty);
}
}
return User;
})();
const user = new User(123);
alert(user.getId()); // 123
user.setId(456);
alert(user.getId()); // 456
这样,拿不到弱映射中的健,也就无法取得弱映射中对应的值。虽然这防止了前面提到的访问,但
整个代码也完全陷入了ES6 之前的闭包私有变量模式。 - DOM 节点元数据
因为WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。来看下面这个例子,其中
使用了常规的Map:
const m = new Map();
const loginButton = document.querySelector(‘#login’);
// 给这个节点关联一些元数据
m.set(loginButton, {disabled: true});
假设在上面的代码执行后,页面被JavaScript 改变了,原来的登录按钮从DOM 树中被删掉了。但
由于映射中还保存着按钮的引用,所以对应的DOM 节点仍然会逗留在内存中,除非明确将其从映射中
删除或者等到映射本身被销毁。
如果这里使用的是弱映射,如以下代码所示,那么当节点从DOM 树中被删除后,垃圾回收程序就
可以立即释放其内存(假设没有其他地方引用这个对象):
const wm = new WeakMap();
const loginButton = document.querySelector(‘#login’);
// 给这个节点关联一些元数据
wm.set(loginButton, {disabled: true});