Set Weakset解析
众所周知,数组一直是js中的一种集合类型(当然,非数组对象也算是一种集合,只不过这种集合是键值对的集合,所以对象和数组的用途也是完全不同的)
再后来的发展中,大家发现其实我们需要一种用非数组对象创建的一种数据结构,这种结构在ES6中被称为Set集合与Map集合。
其实在最开始还没Set集合与Map集合这种概念的时候,我们开发者就有类似的实现方式了
var set = Object.create(null)
set.flag = true
if (set.flag) {
// do something
}
在早期我们会定一个一个原型为null
的对象,不继承任何属性。用这种类似的方式去判断对象中的某个属性值是否存在。
其实Map集合与Set集合两者的区别点在于存储的值不同
var map = Object.create(null)
map.name = 'xiaoming'
var person = map.name
console.log(person)
// 'xiaoming'
当我们的程序简单的时候我们完全可以像上面的写法一样,通过创建原型为null
的对象来实现这种操作,但是当我们程序变得复杂的时候,我们可能就不会那么好实现了:
var map = Object.create(null)
map[5] = 'apple'
console.log(map[5])
// 'apple'
上面的程序在解析的过程中会把我们数字类型的5
,解析称string类型的‘5’
,这样的话,其实我们最初的目的是没有达到的,类似的情况还有像
var map = Object.create(null)
var a = {}
var b = {}
map[a] = 'apple'
console.log(map[b])
// 'apple'
可能有同学会问,这是为啥呢,其实原因很简单,在解析的过程中map[a]
,被解析成了map[object]
,所以导致map[b]
的结果等于map[a]
所以因为这些问题的出现,在ES6中添加了Map集合与Set集合
Set集合
Set类型是一种有序列表,其中每一项都是相互独立的并且是不重复的,通过Set集合我们可以快速的访问其中的数据,有效的追踪各种离散值。
就和上面的概念一样,每一项都是先对独立的并且是不重复的!!! 说到这里大家可能和我像的一样,去重!!!没错使用Set集合可以很快的对一个数组进行去重!!
function unique(arr) {
return [...new Set(arr)]
}
// 或者
function unique1(arr) {
return Array.from(new Set(arr))
}
api
- add() 向集合中添加元素
- size() 获取集合中目前元素的数量
- has() 判断集合中是否存在某个值
- delete() 删除集合中的某一个值
- clear() 移除集合中所有的值
- forEash()
let set1 = new Set()
set1.add(5)
set1.add('5')
console.log(set1.size)
// 2
set1.add(5)
console.log(set1.size)
// 2
let set2 = new Set()
let a = {}
let b = {}
set2.add(a)
set2.add(b)
console.log(set2.size)
// 2
// set2中的a和b不会被转换成字符串,因此他们在集合中还是两个独立的元素
let set3 = new Set()
set3.add(1)
set3.add(2)
set3.delete(1)
console.log(set3.size)
// 1
set3.add(4)
set3.clear()
set3.has(1) // false
set3.size // 0
下面大家看一个比较有意思的例子
set1.has(5)
set2.has({})
大家可以看一下上面的这两个值分别是什么,为什么?😄
forEach

在Set集合中forEach的回调函数也有三个参数
- 当前元素
- 与第一个参数相同
- 当前Set集合
其实理解起来可能会有点别扭,为啥第一个参数和第二个参数表示同一个值!? 其实在Map集合和数组中forEach的回调函数都接受三个值,所以为了避免Set、Map、数组之间forEach这个api的歧义过大,最终ECMAScript6标准指定委员会决定让Set的forEach这个api与其他的保持一致,也接受三个参数。
在Set集合的forEach()方法中,第二个参数和数组是一样的this
。当然我们在写代码的过程中也可以使用=>
(箭头函数)来代替传递this的这种操作。
Set集合中还有keys()和values()两个api,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

WeakSet集合
由于在Set的实例中存储对象于存储在变量中完全一样,所以只要Set实例存在,那么垃圾回收机制就不能释放该对象的内存空间,因此Set类型又可以被看作是一种强类型的Set集合
举个栗子:在web页面中通过js代码记录了一些DOM元素,这些元素有可能呗另外一段代码或者脚本移除,但是你又不想自己的代码保留对DOM元素的最后引用,也就是咱们经常说的内存泄漏的问题!
为了解决这个问题,ECMAScript6中引入了一种弱类型的Set集合 WeakSet集合
WeakSet支持add,clear,delete,has四个api
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false, 对象 foo 并没有被添加进 ws 中
ws.delete(window); // 从集合中删除 window 对象
ws.has(window); // false, window 对象已经被删除了
ws.clear(); // 清空整个 WeakSet 对象
区别
需要注意的Set和WeakSet两者很大的区别,一方面WeakSet是只能保存对象值的弱引用,另外还有下面几个差别:
- 对于 WeakSet 的实例,若调用add()方法时传入了非对象的参数,就会抛出错误(has()或delete()则会在传入了非对象的参数时返回false);
- Weak Set 不可迭代,因此不能被用在 for-of 循环中;
- Weak Set 无法暴露出任何迭代器; (例如 keys()与values() 方法,因此没有任何编程手段可用于判断 Weak Set 的内容);
- Weak Set 没有 forEach() 方法;
- Weak Set 没有 size 属性
WeakSet之所以不可遍历是由于WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。
虽然WeakSet集合的功能看似受限,其实是为了让它能够正确的处理内存中的数据。所以,当你只需要追踪对象引用,你就应该是使用WeakSet集合而不是Set集合。