一、Set
1. Set的基本用法
个人认为Set
可以被看做一个元素不重复的数组,因为我们之前做数组去重时经常用到Set
:
const a = [1, 1, 2, 2, 3]
a.add(3)
a = [...new Set(a)] // [1, 2, 3]
不同的是,Set
数据结构无法向数组那样使用元素的索引来取值,例如:
const a = [1, 2, 3]
const b = new Set(a)
a[0] // 1
b[0] // undefined
另外,数组是通过length
表示长度,而Set
则是通过size
表示长度
Set
数据结构可以使用Set()
方法进行实例化,Set()
方法接受一个参数,这个参数可以是一个具有iterable
接口的数据机构(比如数组,字符串,NodeList
等):
// 例一
const set = new Set([1, 2, 3, 4, 4])
set.size // 4
// 例二
const items = new Set('string')
items.size // 6
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
2. Set实例的属性
Set
实例具有以下属性:
- Set.prototype.constructor: 构造函数, 默认为
Set()
方法 - Set.prototype.sizee: 返回
Set
实例的成员总数
const set = new Set.prototype.constructor([1, 2])
set.size // 2
3. Set实例的方法
Set
数据结构有两大类方法,分别是操作方法和遍历方法。
3.1 操作方法
-
Set.prototype.add(value)
:添加某个值,返回 Set 结构本身。 -
Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。 -
Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set的成员。 -
Set.prototype.clear()
:清除所有成员,没有返回值。
Set.prototype.add()
方法示例代码:
const set = new Set([1, 2, 2, 3])
set.size // 3
set.add(4)
set.add(4)
set.size // 4
set.add([1])
set.add([1])
set.size // 6
set.add(+0)
set.add(-0)
set.size // 7
NaN === NaN // false
set.add(NaN)
set.add(NaN)
set.size // 8
通过上面的代码我们可以发现Set.prototype.add()
方法添加了不同重复值之后,set
的表现也不同,这是为什么呢?因为,Set
判断元素是否相同采用的是“完全相等”即===
操作,当传入的参数是对象等引用类型时,尽管从字面上看起来是一样的,但是因为其指向了不同的内存地址,所以,他们是不相等的,这一点要特别注意。但是也有一个例外,就是NaN
会被当做重复值。
Set.prototype.delete()
方法、Set.prototype.has()
和Set.prototype.clear()
示例代码:
const set = new Set([1, 2])
set.has(1) // true
set.delete(1) // true
set.has(1) // false
set.delete(1) // false
set.clear()
set.size // 0
3.2 遍历方法
Set
结构的实例有四个遍历方法,可以用于遍历成员:
-
Set.prototype.keys()
:返回键名的遍历器 -
Set.prototype.values()
:返回键值的遍历器 -
Set.prototype.entries()
:返回键值对的遍历器 -
Set.prototype.forEach()
:使用回调函数遍历每个成员
keys()
、values
、entries
方法返回的都是遍历器对象, 和Object
的同名方法表现基本一致,不过,因为Set
数据结构没有键名,所以keys()
与values
返回的内容一致,都是Set
的值:
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
从上述代码可以看出Set
结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。
forEach()
方法也和数组的方法表现极其类似,该方法接受一个函数作为参数,该函数有三个参数,依次是键值,键名,集合本身。另外,如果我们还想使用数组的其他方法,比如filter()
, map()
等方法,我们可以通过扩展运算符将Set
转为数组就可以使用啦~
二、WeakSet
WeakSet
结构与Set
类似,也是不重复的值的集合。但是,它与Set
有两个区别。
首先,WeakSet
的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
上面代码试图向 WeakSet
添加一个数值和Symbol
值,结果报错,因为WeakSet
只能放置对象。
其次,WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为0
,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet
里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet
适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet
里面的引用就会自动消失。
由于上面这个特点,WeakSet
的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet
内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet
不可遍历。
2.1 WeakSet的使用
同Set
一样,WeakSet
通过WeakSet()
方法生成,该方法接受一个参数,该参数必须是有遍历器接口的,并且,该对象的元素也必须为对象。
const a = [1, 2]
const b = [[1], [2]]
const wkset = new WeakSet(a) // Invalid value used in weak set
const wkset_cp = new WeakSet(b) // WeakSet {Array(1), Array(1)}
2.2 WeakSet的方法
WeakSet
和Set
数据结构比较类似,所以,方法也相同,不同的是,因为WeakSet
特殊原因导致的不可遍历,所以,它没有遍历方法,也没有clear()
方法。
参考链接
作者:阮一峰
链接:http://es6.ruanyifeng.com/#docs/destructuring