1、Set
基本用法
ES6提供了新的数据结构Set。类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成Set数据结构。
const s = new Set();
[2,3,5,4,5,2,2].forEach(x=> s.add(x));
for (let i of s){
console.log(i);
}// 2 3 5 4
上边代码通过add()方法向Set结构加入成员,结果表明Set结构不会添加重复的值。
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1,2,3,4,4]);
[...set]//[1,2,3,4]
Set实例的属性和方法
Set结构的实例有以下属性
- Set.prototype.constructor:构造函数,默认就是Set函数
- Set.prototype.size:返回Set实例的成员总数
Set实例的方法分为两大类:操作方法和遍历方法。四种操作方法为
- Set.prototype.add(value):添加某个值,返回Set结构本身。
- Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
- Set.prototype.clear():清除所有成员,没有返回值
Array.from()方法可以将Set结构转为数组。
遍历操作
Set结构的实例有四个遍历方法,可以用于遍历成员
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- Set.prototype.forEach():使用回调函数遍历每个成员
Set的遍历顺序就是插入顺序。
(1)keys(),values(),entries()
keys方法、values方法、entries方法返回的都是遍历器对象。由于Set结构没有键名,只有键值,所以keys方法和values方法的行为完全一致。
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"]
(2) forEach()
Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值
let set = new Set([1,4,9]);
set.forEach((value,key)=>console.log(key+':'+value)
// 1:1
//4:4
//9:9
forEach方法的参数就是一个处理函数。该函数与数组的forEach一致,依次为键值、键名、集合本身。
另外,forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
(3)遍历的应用
拓展字符串内部使用for…of循环,所以也可以用于Set结构。
let set = new Set(['red','green','blue']);
let arr = [...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可以很容易地实现并集、交集和差集
let a = new Set([1,2,3]);
let b= new Set([4,3,2]);
//并集
let union = new Set([...a,...b]);
//交集
let intersect = new Set([...a].filter(x => b.has(x)));
//a相对于b的差集
let difference = new Set([...a].filter(x=>!b.has(x)));
如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,有以下两种变通方法:1、利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构。2、利用Array.from方法
//方法一
let set = new Set([1,2,3]);
set = new Set([...set].map(val => val*2));//{2,4,6}
//方法二
let set = new Set([1,2,3]);
set = new Set(Array.from(set,val => val*2));
2、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 之中。
这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。
这些特点同样适用于本章后面要介绍的 WeakMap 结构。
语法
WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构
const ws = new WeakSet();
作为构造函数,WeakSet可以接受一个数组或者类似数组的对象作为参数。该数组的所有成员都会自动成为WeakSet实例对象的成员
const a = [[1,2],[3,4]];
const ws = new WeakSet(a);
//WeakSet {[1,2],[3,4]}
上边代码中,a是一个数组,有两个成员,也都是数组。将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。注意:是a数组的成员成为WeakSet的对象,而不是a数组本身,这意味着数组的成员只能是对象。
const b = [1,2];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
数组b的成员不是对象,加入WeakSet就会报错。
WeakSet的三个方法
- WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。
- WeakSet.prototype.delete(value):删除WeakSet实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否存在
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没有size属性,没有办法遍历它的成员。
ws.size // undefined
ws.forEach //undefined
ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function
WeakSet不能遍历,因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处是储存DOM节点,而不用担心这些节点从文档移除时会引发内存泄漏。
const foos = new WeakSet()
class Foo{
constructor(){
foos.add(this)
}
method(){
if(!foos.has(this)){
throw new TyprError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用 WeakSet 的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。