1.Set类型简介
ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值
Set本身是一个构造函数,用来生成Set数据结构
1 const s = new Set(); 2 [1,2,3,4,4,3,2,1].forEach( e => s.add(e)) 3 4 for(let i of s){ 5 console.log(i); 6 } 7 // 1,2,3,4
上面代码通过add()方法向Set结构加入成员,结果表明Set结构不会添加重复的值
Set函数可以接受一个数组(或者具有Iterable接口的其他数据结构)作为参数,用来初始化
1 // 例一 2 const set = new Set([1, 2, 3, 4, 4]); 3 [...set] 4 // [1, 2, 3, 4] 5 6 // 例二 7 const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); 8 items.size // 5 9 10 // 例三 11 const set = new Set(document.querySelectorAll('div')); 12 set.size // 56 13 14 // 类似于 15 const set = new Set(); 16 document 17 .querySelectorAll('div') 18 .forEach(div => set.add(div)); 19 set.size // 56
上面的办法展示了一种去除数组重复成员的方法:
const arr = [1,1,3,3,4,4,2] const newArr = [...new Set(arr)] newArr // [1, 3, 4, 2]
或者也可以用于去除字符串里面的重复字符:
let str = "hello world" let newStr = [...new Set(str.split(""))].join("") newStr // "helo wrd"
向Set加入值得时候,不会发生类型转换,所以 5 和 "5" 是两个不同的值。Set内部判断两个值是否不同使用的算法叫做'Same-Value-zero equality",它类似于精确相等运算符(===)。主要的区别是向 Set 加入值时认为NaN
等于自身,而精确相等运算符认为NaN
不等于自身。
let set = new Set(); let a = NaN; let b = NaN; set.add(a); set.add(b); set // Set {NaN}
上面代码向 Set 实例添加了两次NaN
,但是只会加入一个。这表明,在 Set 内部,两个NaN
是相等的。
另外,两个对象总是不相等的。
let set = new Set() set.add({}) set.add({}) set.size // 2
上面代码表示,由于两个空对象不相等,所以它们被视为两个值。
2.Set实例的属性和方法
查看Set类型的原型对象可以看到Set结构的实例有以下属性。
(1)属性
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
(2)方法
Set实例的方法分为两大类:
- 操作方法(用于操作数据)
- Set.prototype.add(value):添加某个值,返回Set结构本身
- Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员
- Set.prototype.clear():清除所有成员,没有返回值
- 实例:
-
1 s.add(1).add(2).add(2); 2 // 注意2被加入了两次 3 4 s.size // 2 5 6 s.has(1) // true 7 s.has(2) // true 8 s.has(3) // false 9 10 s.delete(2); 11 s.has(2) // false
-
- 遍历方法(用于遍历成员)
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- 实例:
-
1 let set = new Set(['red', 'green', 'blue']) 2 3 for (let item of set.keys()) { 4 console.log(item); 5 } 6 // red 7 // green 8 // blue 9 10 for (let item of set.values()) { 11 console.log(item); 12 } 13 // red 14 // green 15 // blue 16 17 for (let item of set.entries()) { 18 console.log(item); 19 } 20 // ["red", "red"] 21 // ["green", "green"] 22 // ["blue", "blue"]
-
- 说明:
entries
方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。
- 实例:
- Set.prototype.forEach():使用回调函数遍历每个成员Set 结构的实例与数组一样,也拥有
forEach
方法,用于对每个成员执行某种操作,没有返回值。注意:Set的遍历顺序就是插入顺序 - 实例:
-
1 let set = new Set([1, 4, 9]); 2 set.forEach((value, key) => console.log(key + ' : ' + value)) 3 // 1 : 1 4 // 4 : 4 5 // 9 : 9
- 上面代码说明,
forEach
方法的参数就是一个处理函数。该函数的参数与数组的forEach
一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。
-
- 遍历方法的应用:
- 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
-
let arr = [3, 5, 2, 2, 5, 5]; let unique = [...new Set(arr)]; // [3, 5, 2]
-
- 数组的
map
和filter
方法也可以间接用于 Set 了。
-
let set = new Set([1, 2, 3]); set = new Set([...set].map(x => x * 2)); // 返回Set结构:{2, 4, 6} let set = new Set([1, 2, 3, 4, 5]); set = new Set([...set].filter(x => (x % 2) == 0)); // 返回Set结构:{2, 4}
-
- 使用Set实现并集(Union)、交集(Intersect)、差集(Difference):
-
let a = new Set([1,2,3]) let b = new Set([3,4,5]) // 并集: let union = new Set([...a,...b]) // union : {1, 2, 3, 4, 5} // 交集 let intersect = new Set([...a].filter( e => b.has(e) )) // {3} // 差集 (a 相对于 b的差集) let difference = new Set([...a].filter(e => !b.has(e))) // {1, 2}
-
- 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
- 如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用
Array.from
方法。-
1 // 方法一 2 let set = new Set([1, 2, 3]); 3 set = new Set([...set].map(val => val * 2)); 4 // set的值是2, 4, 6 5 6 // 方法二 7 let set = new Set([1, 2, 3]); 8 set = new Set(Array.from(set, val => val * 2)); 9 // set的值是2, 4, 6
-
3.WeakSet
WeakSet结构与Set类似,也是不重复的值的集合。但是与Set有两个区别。
(1)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
(2)WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用
也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为0
,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。