什么是 weakmap, 和 map 有什么区别
弱引用的定义
在 JavaScript 中,弱引用(Weak Reference)是一种特殊类型的引用,它不会阻止其引用的对象被垃圾回收器回收。相比于常规的强引用,弱引用并不增加被引用对象的引用计数,因此被弱引用关联的对象可以在没有其他强引用时被回收。
weakMap
WeakMap 是一种键值对的集合,其中键是弱引用,只能是对象,并且值可以是任意类型。当键对象被回收时,与之关联的值也会被自动清除。
WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。
注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。
-
属性:
- constructor:构造函数
-
方法:
- has(key):判断是否有 key 关联对象
- get(key):返回key关联对象(没有则则返回 undefined)
- set(key):设置一组key关联对象
- delete(key):移除 key 的关联对象
区别有以下四点
引用方式:WeakMap 中的键必须是对象,而 Map 中的键可以是任意类型的值(包括对象、原始类型等)。
引用关系:在 WeakMap 中,键是弱引用,不会阻止与之关联的对象被垃圾回收,而在 Map 中,键是强引用,会阻止与之关联的对象被垃圾回收。
遍历方式:WeakMap 不支持直接遍历,没有提供类似 forEach 或 keys 方法,而 Map 可以使用 forEach、keys、values 等方法进行遍历。
大小和性能:WeakMap 没有提供类似 size 属性来获取元素数量,也没有提供 clear 方法清空所有元素。在性能方面,由于 WeakMap 的实现方式不同,对于大型数据集,WeakMap 可能比 Map 在存取速度上更高效。
weakmap的key必须是对象,而map可以是任何值
const weakMap = new WeakMap();
const key1 = "key1";
const key2 = 123;
const key3 = { name: "John" };
// 使用非对象作为键,会引发 TypeError
weakMap.set(key1, "value1"); // TypeError: Invalid value used as weak map key
weakMap.set(key2, "value2"); // TypeError: Invalid value used as weak map key
// 使用对象作为键
weakMap.set(key3, "value3");
console.log(weakMap.get(key3)); // 输出: value3
无法用 foreach 或者 keys() 遍历
const map = new Map();
const weakMap = new WeakMap();
const key1 = { name: 'John' };
const key2 = { name: 'Alice' };
map.set(key1, 'value1');
map.set(key2, 'value2');
weakMap.set(key1, 'value1');
weakMap.set(key2, 'value2');
// 使用 forEach 遍历 Map
map.forEach((value, key) => {
console.log(key, value);
});
// 输出:
// { name: 'John' } value1
// { name: 'Alice' } value2
// 使用 keys 遍历 Map
for (const key of map.keys()) {
console.log(key, map.get(key));
}
// 输出:
// { name: 'John' } value1
// { name: 'Alice' } value2
// 尝试使用 forEach 遍历 WeakMap,会抛出 TypeError
weakMap.forEach((value, key) => {
console.log(key, value);
});
// TypeError: weakMap.forEach is not a function
// 尝试使用 keys 遍历 WeakMap,会抛出 TypeError
for (const key of weakMap.keys()) {
console.log(key, weakMap.get(key));
}
// TypeError: weakMap.keys is not a function
场景一: 引用 DOM 元素
当我们需要为一些DOM变量建立一个映射关系时,我们可以使用Map,但不能用Object,因为Object的键是字符串类型,会进行隐式类型转换。
将一个按钮对象实例作为键存储在Map中,并将点击次数作为其值。
let btn1 = document.getElementById("btn");
const map = new Map();
map.set(btn1, 0);
btn1.addEventListener('click',
() => map.set(btn1, map.get(btn1) + 1)
);
即便变量btn1不再指向这个按钮对象实例,但是由于Map依旧持有这个实例的引用,因此它的引用计数为1,这会导致内存泄漏,因为这个实例不能被垃圾回收,除非我们手动解除引用。
使用WeakMap来代替Map,既能实现相同功能,又不会影响垃圾回收的正常进行。
let btn1 = document.getElementById("btn");
const map = new WeakMap();
map.set(btn1, 0);
btn1.addEventListener('click',
() => map.set(btn1, map.get(btn1) + 1)
);
WeakMap持有的是这个按钮对象实例的弱引用,不会占用它的引用计数,因此这个按钮从DOM上删除或不被任何变量引用时,便被GC掉。
场景二: 实现对象缓存
弱引用还可以用来实现缓存。例如用弱哈希表,即通过弱引用来缓存各种引用对象的哈希表。当垃圾回收器运行时,假如应用程序的内存占用量高到一定程度,那些不再被其它对象所引用的缓存对象就会被自动释放。
const cache = new WeakMap();
// basket是一个数组
function computeTotalPrice(basket) {
if (cache.has(basket)) {
return cache.get(basket);
} else {
let total = 0;
basket.forEach(itemPrice => total += itemPrice)
cache.set(basket, total)
return total;
}
}
场景三: 检测对象的循环引用
这其实也是对象缓存的一种。下面是一个对象深克隆方法的栗子,只是一个简单实现。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;
function deepClone(target, map = new WeakMap()) {
if (map.get(target)) {
return target;
}
// 获取当前值的构造函数:获取它的类型
let constructor = target.constructor;
// 检测当前对象target是否与正则、日期格式对象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
// 创建一个新的特殊对象(正则类/日期类)的实例
return new constructor(target);
}
if (isObject(target)) {
map.set(target, true); // 为循环引用的对象做标记
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
场景四: 实现类的私有变量
目前,js中的类的熟悉和方法都是公有的,但tc39中有个关于类的私有变量的草案:tc39/proposal-class-fields,未来有可能成为标准。
现在,我们可以利用WeakMap是一个黑盒的特性,来实现类的私有变量:
let _data = new WeakMap();
class Registry {
constructor(person, action) {
const data = []
_data.set(this, data);
}
get() {
return _data.get(this)
}
set(person, action){
const data = _data.get(this)
data.push([person,action])
_data.set(this, data);
}
}
这里不仅利用了WeakMap的特性,也利用了闭包。