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 函数能够接受一个数组(或者具备iterable接口的其余数据结构)做为参数,用来初始化,下面是两种利用set进行数组去重的方式:函数
const arr = [1,2,2,3]; //方式一 console.log(Array.from(new Set(arr))); //方式二 console.log([...new Set(arr)]);
向 Set 加入值的时候,不会发生类型转换,因此5和"5"是两个不一样的值。Set 内部判断两个值是否不一样,使用的算法叫作“Same-value equality”,它相似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。this
let set = new Set(); let a = NaN; let b = NaN; set.add(a); set.add(b); set // Set {NaN}
上面代码向 Set 实例添加了两个NaN,可是只能加入一个。这代表,在 Set 内部,两个NaN是相等。prototype
Set 结构的实例有如下属性:code
Set 实例的方法分为两大类:操做方法(用于操做数据)和遍历方法(用于遍历成员)。下面先介绍四个操做方法:对象
下面是一个对比,看看在判断是否包括一个键上面,Object结构和Set结构的写法不一样:接口
// 对象的写法 const properties = { 'width': 1, 'height': 1 }; if (properties[someName]) { // do something } // Set的写法 const properties = new Set(); properties.add('width'); properties.add('height'); if (properties.has(someName)) { // do something }
Set 结构的实例有四个遍历方法,能够用于遍历成员:内存
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"] // ["green", "green"] // ["blue", "blue"]
Set 结构的默认遍历器生成函数就是它的values方法,这意味着,能够省略values方法,直接用for...of循环遍历 Set:
Set.prototype[Symbol.iterator] === Set.prototype.values // true let set = new Set(['red', 'green', 'blue']); for (let x of set) { console.log(x); } // red // green // blue
Set 结构的实例与数组同样,也拥有forEach方法,用于对每一个成员执行某种操做,没有返回值
set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + ' : ' + value)) // 1 : 1 // 4 : 4 // 9 : 9
扩展运算符(...)内部使用for...of循环,结合数组的map和filter方法,很容易实现交集、并集、差集:
let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // 差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
若是想在遍历操做中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,而后赋值给原来的 Set 结构;另外一种是利用Array.from方法:
// 方法一 let set = new Set([1, 2, 3]); set = new Set([...set].map(val => val * 2)); // set的值是2, 4, 6 // 方法二 let set = new Set([1, 2, 3]); set = new Set(Array.from(set, val => val * 2)); // set的值是2, 4, 6
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 里面的引用就会自动消失。
const weakSet = new WeakSet(); let obj = {}; weakSet.add(obj); console.log(weakSet.has(obj));//true obj = null; console.log(weakSet.has(obj));//false
因为上面这个特色,WeakSet的成员是不适合引用的,由于它会随时消失。另外,因为WeakSet内部有多少个成员,取决于垃圾回收机制有没有运行,运行先后极可能成员个数是不同的,而垃圾回收机制什么时候运行是不可预测的,所以 ES6规定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 = [3, 4]; const ws = new WeakSet(b); // Uncaught TypeError: Invalid value used in weak set(…)
WeakSet 结构有如下三个方法:
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 节点,而不用担忧这些节点从文档移除时,会引起内存泄漏。
下面是 WeakSet 的另外一个例子:
const foos = new WeakSet() class Foo { constructor() { foos.add(this) } method () { if (!foos.has(this)) { throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!'); } } }
上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,因此删除实例的时候,不用考虑foos,也不会出现内存泄漏。