? 前言:之前学习ES6中Set和Map只管用就完事了,完全忽略了WeakSet和WeakMap,结果面试官一问就懵逼了,赶紧踏踏实实学习学习
WeakSet
为啥需要WeakSet
要弄懂这个问题,首先需要了解JS的垃圾回收机制,JS现代教程写得十分清楚明白,先补习一波,就可以知道如果一个对象没有被引用,则会被视为垃圾并回收该对象的内存空间,当我们将对象存储在Set的实例中与存储在下图的user变量一样,只要Set实列中的引用存在,垃圾回收机制就不能将该对象视为垃圾并清除。
let set =new Set(),key={};
set.add(key)
console.log(set.size) //1
//移除原始引用
key=null
console.log(set.size) //1
key=[...set][0] //取回原始引用
复制代码
上例中就是当我们设置key的值为null,清除了原始引用,但Set实例中任存在着一个对该对象的强引用,导致最终我们还能访问到该对象,这样的话会导致内存无法释放,进而可能会引发内存泄漏。所以,为了解决这个问题,WeakSet类型就出现了,看名字就可以知道,弱引用Set集合,WeakSet 只存储对象的弱引用,垃圾回收机制不考虑 WeakSet 对该对象的引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存。
特性
- WeakSet结构同样不会存储重复的值,它的成员只能是对象类型。
- 因为WeakSet 是弱引用,垃圾回收机制可能随时清除其中的对象,所以不可以进行
forEach( )
遍历、for-of
循环等迭代操作 - 也是因为弱引用,WeakSet 结构没有keys( ),values( ),entries( )等方法和size属性
声明定义
WeakSet 是一个构造函数,可以接受一个数组或类似数组的对象作为参数 ,可以使用new
命令,创建 WeakSet 数据结构。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
复制代码
基本操作
由于弱引用带来的限制,WeakSet只支持三种基本操作。
- WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
复制代码
具体使用场景
根据上文我们可以得知,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。具体一个场景就是存储DOM对象,当我们存储的DOM对象元素被另外一段脚本移除,我们也不想保留这些元素的引用而造成内存泄漏,就可以使用WeakSet来存储。
?存储DOM元素
const ws = new WeakSet();
document.querySelectorAll("button").forEach(item => Ws.add(item));
复制代码
WeakMap
为啥需要WeakMap
相对的,WeakMap也是弱引用的Map集合,所以同理,希望避免Map集合中保存对象的强引用而导致的内存泄漏问题,使用WeakMap数据结构来保存对象的弱引用,与WeakSet不同的是WeakMap中存储的是键值对,WeaMap对键名是弱引用的,键值是正常引用,如果键在其他地方不被引用时,垃圾回收机制就会自动回收这个对象所占用的内存空间,同时移除WeakMap中的键值对,但键名对应的值如果是一个对象,则保存的是对象的强引用,不会触发垃圾回收机制被回收。
特性
- WeakMap中存储的是许多键值对的无序列表,列表的键名必须是非null的对象,对应的值可以是任意类型
- WeaMap对键名是弱引用的,键值是正常引用
- 因为WeakMap 是弱引用,垃圾回收机制可能随时清除其中的对象,所以不可以进行
forEach( )
遍历等操作 - 因为弱引用,WeaMap 结构没有keys( ),values( ),entries( )等方法和 size 属性
声明定义
- WeakMap 可以使用 set 方法添加成员
- WeakMap 也可以接受一个数组,作为构造函数的参数
// 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"
复制代码
基本操作
因为垃圾回收机制的运行无法预测,所以 没有办法列出所有键名,某个键名是否存在完全不可预测,,为了防止出现不确定性,就统一规定不能取到键名,二是无法清空,即不支持clear
方法。因此,WeakMap
只有四个方法可用:get()
、set()
、has()
、delete()
。
- WeakMap.prototype.set(key,value):向 WeakMap 实例添加一个新成员,当前的
WeakMap
对象。 - WeakMap.prototype.delete(key):清除 WeakMap 实例的指定成员,返回true。如果删除失败,返回false。。
- WeakMap.prototype.has(key):返回一个布尔值,表示某个值是否在 WeakMap 实例之中。
- WeakMap.prototype.get(key):get方法读取key对应的键值,如果找不到key,返回undefined
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
wm.get(key); //{foo: 1}
wm.has(key); //true
wm.delete(key);//true
复制代码
具体使用场景
WeakMap
的专用场合就是,它的键所对应的对象,可能会在将来消失,如果我们要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
?存储DOM元素
将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element
的引用就是弱引用,一旦将这个 DOM 节点删除,该element
对象就会自动被垃圾回收机制清除,不存在内存泄漏风险。
<body>
<div>bar</div>
<div>foo</div>
</body>
<script>
const wm = new WeakMap();
document
.querySelectorAll("div")
.forEach(item => wm.set(item, item.innerHTML));
console.log(wm); //WeakMap {div => "bar", div => "foo"}
</script>
复制代码
?存储私有变量
ES5中我们经常利用立即执行函数的方式来设置私有变量,但问题是私有变量不会随着实例对象的销毁被回收,WeakMap正好可以解决这个问题。
let Person=(function(){
let privateData = new WeakMap();
function Person(name){
privateData.set(this,{name})
}
Person.prototype.getName = function(){
return privateData.get(this).name
}
return Person
}())
复制代码
当调用Person构造函数时,实例就会被添加到WeakMap集合中,键是this, 是实例的弱引用,值是私有属性name的对象, 如果删除实例,私有属性也就随之消失,不会造成内存泄漏。