ES6 新增的弱映射 (WeakMap) 是一种新的集合类型,WeakMap 是 Map 的 “兄弟” 类型,其 API 也是 Map 的子集
WeakMap 基本 API
实例化弱映射
有参和无参构造
// 初始化一个空的 弱映射关系
const wm = new WeakMap();
// 弱映射只能以对象为键
const objKey1 = { id: 1 };
const objKey2 = { id: 2 };
const objKey3 = { id: 3 };
// 初始化填充的弱映射
const wm2 = new WeakMap([
[objKey1, "any value1"],
[objKey2, "any value2"],
[objKey3, "any value3"],
]);
// 获取值
log(wm2.get(objKey1)); // log: any value1
初始化是一个事物操作
如果因为在初始化的时候出现无效的键,则会导致整体初始化失败
// 初始化是一个 事务操作,如果其中一个键无效,会导致整个初始化失败
const wm3 = new WeakMap([
[objKey1, "any value1"],
["invalidKey", "anyVal2"], // 因为无效的键导致初始化失败 ❌
]);
// 如上代码将出错 TypeError: Invalid value used as weak map key
WeakMap「弱」的含义
弱引用的意思是不属于正式的引用,当键不存在被其他引用的时候不会影响垃圾回收,当键被垃圾收回之后,这个键值对就会从弱映射中消失
const wm4 = new WeakMap();
wm4.set({}, "val");
这个 demo 中 set 方法初始化一个新对象并将它作为一个字符串的键,因为没有指向这个对象的其他引用,
所以这行代码执行完毕后,这个对象键就会被当作垃圾回收,因此这个对象键和其对应的值也会消失在这个弱映射中,这个值也没有被引用(基本类型),所以在键值对就小时候,值也会成为垃圾回收的目标
const wm = new WeakMap();
const container = {
key: {},
};
wm.set(container.key, "val");
function removeReference() {
container.key = null;
}
log(wm.get(container.key)); // log: val
// container 维护者一个对弱映射的引用,因此这个对象键不会成为垃圾回收的目标。
// 调用了 removeReference 就会摧毁键对象的最后一个引用,垃圾回收程序就会把这个 键值对 清理掉
removeReference();
log(wm.get(container.key)); // log: undefined
WeakMap 的应用
1. 模拟私有变量
const createUserById = (id) => {
// 利用闭包将 vm 放在特定的作用域,防止外部获取后可以自由获取值
const wm = new WeakMap();
class User {
constructor(id) {
this.idProperty = Symbol("id");
this.setId(id);
}
setPrivate(property, value) {
// this 代表的就是当前的实例对象
const privateMembers = wm.get(this) || {};
privateMembers[property] = value;
// wm 以 当前实例作为 key, privateMembers 作为值进行储存
wm.set(this, privateMembers);
}
getPrivate(property) {
return wm.get(this)[property];
}
setId(id) {
this.setPrivate(this.idProperty, id);
}
getId() {
return this.getPrivate(this.idProperty);
}
}
return new User(id);
};
const user = createUserById("id_230611");
log(user.getId()); // id_230611
user.setId(456);
log(user.getId()); // 456
2. DOM 节点元数据
WeakMap 实例不会妨碍垃圾回收,非常适合保存关联数据,WeakMap 不会影响 垃圾回收的 操作
<body>
<button id="login">login button</button>
<script>
const m = new Map();
const loginBtn = document.querySelector("#login");
// 给 loginBtn 关联一些数据
m.set(loginBtn, { disabled: true });
/**
* 在执行上述的代码之后,页面被 js 改变了,
* 登陆按钮 从 DOM 树中删除了,带式犹豫映射中还保存着按钮的引用,
* 所以对应的 DOM 节点仍然会被保留在内存中,除非明确将其从映射中删除或者等到映射本身被销毁
* 为了解决上述问题,还得看 弱映射,如果是弱映射,当节点从 DOM 书中删除后,垃圾回收程序就可以立即释放其内存(如果其他地方没有应用这个 loginBtn 节点)
* */
const wm = new WeakMap();
wm.set(loginBtn, { disabled: true });
</script>
</body>