ES5中的 Set
在ES5中,开发者使用对象属性来模拟Set与Map,就像这样:
let set = Object.create(null);
set.foo = true;
//检查属性的存在性
if (set.foo){
//一些操作
}
本例中的set 变量是一个原型为null的对象,确保在此对象上没有继承属性。使用对象的属性作为需要检查的唯一值在ES5中是很常用的方法。当一个属性被添加到set对象时,它的值也被设为true,因此条件判断语句(例如本例中的if 语句)就可以简单判断出该值是否存在。
使用对象模拟Set与模拟Map之间唯一真正的区别所存储的值。
ES6的Set
ES6新增了set类型,这是一种无重复值的有序列表。Set允许对它包含的数据进行快速访问,从而增加了一个追踪离散值的更有效方式。
创建Set并添加项目
Set使用 new Set() 来创建,而调用 add() 方法就能向Set 中添加项目,检查size属性还能查看其中包含有多少项:
let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); //2
Set 不会使用强制类型转换来判断是否重复。这意味着Set可以同时包含数值5与字符串*“5”*,将它们都作为相对独立的项(Set内部的比较使用了Object.is())方法,来判断两个值是否相等,唯一的例外是+0 与 -0在Set中被判断为是相等的)。你还可以向Set添加对个对象,它们不会被合并为同一项:
let set = new Set().
key1 = {},
key2 = {};
set.add(key1);
set.add(key2);
console.log(set.size); //2
由于key1 与key2 并不会被转换为字符串,所以它们在这个Set内部被认为是两个不同的项。
如果*add()*方法用相同值进行了多次调用,那么在第一次之后的调用实际上会被忽略:
let set = new Set();
set.add(5);
set.add("5");
set.add(5); //重复了,该调用被忽略
console.log(set.size); //2
你可以使用数组来初始化一个Set,并且Set构造器会确保不重复地使用这些值。
let set= new Set([1,2,3,4,5,5,5,5,5]);
console.log(set.size); //5
若是把已存在的代码或JSON结构转换成为Set来使用,这种特性会让转换更轻松。
Set 构造器实际上可以接受任意可迭代对象作为参数。能使用数组是因为它们默认就是可迭代的,Set与Map也是一样。Set 构造器会使用迭代器来提取参数中的值。
你可以使用*has()*方法来测试某个值是否存在于Set中,就像这样:
let set = new Set();
set.add(5)
set.add("5");
console.log(set.has(5)); //true
console.log(set.has(6)); //false
移除值
使用*delete()方法来移除单个值,或调用clear()*方法来将所有值从Set中移除。
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); //true
set.delete(5);
console.log(set.has(5)); //false
console.log(set.size); //1
set.clear();
console.log(set.has("5")); //false
console.log(set.size); //0
Set上的forEach()方法
与数组的遍历相似。
let set = new Set([1,2]);
let processor = {
output(value){
console.log(value);
}
process(dataSet){
dataSet.forEach((valule)=> this.output(value));
}
};
processor.process(set);
本例中的箭头函数读取了包含它的*process()函数的this值,因此就能正确地将this.output()*解析为调用 processor.output(),
虽然Set能非常好地追踪值,并且 forEach() 可以让你按顺序处理每一项,但是却无法像数组那样用索引来直接访问某个值。如果你想这么做,最好的选择是将Set转换为数组。
将Set 转换为数组
将数组转换为Set相当容易,因为可以将数组传递给Set 构造器;而使用扩展运算符也能简单地将Set转换回数组。例如
let set = new Set([1,2,3,3,3,4,5]),
array = [...set];
console.log(array);
此处的Set 在初始化时载入了一个包含重复值的数组。Set清除了重复值后,又使用了扩展运算符将自身的项放到了一个新数组中,而这个Set仍然包含在创建时所接收的项,这些项只是被复制到了新数组中,而并未从Set中消失。
当已经存在一个数组,而你想用它创建一个无重复值的新数组时,该方法十分有用。
function eliminateDuplicates(items){
return [...new Set(items)];
}
let numbers = [1,2,3,3,3,4,5],
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates);
在*eliminateDuplicates()*函数中,Set只是一个临时的中介物,以便在创建一个无重复数组之前将重复值过滤掉。
Weak Set
由于Set 类型存储对象引用的方式,它也可以被称为Strong Set。对象存储在Set 的一个实例中时,实际上相当于把对象存储在变量中。只要对于Set 实例的引用仍然存在,所存储的对象就无法被垃圾回收机制回收,从而无法释放内存。
let set = new Set(),
key = {};
set.add(key);
console.log(set.size); //1
//取消原始引用
key = null;
console.log(set.size);
// 重新获得原始引用
key = [...set][0];
在本例中,将key设置为null 清除了对key 对象的一个引用,但是另一个引用还存在于set内部。你仍然可以使用扩展运算符将Set 转换为数组,然后访问数组的第一项,key变量就取回了原先的对象。这种结果在大部分程序中是没问题的,但有时,当其他引用消失之后若Set内部的引用也能消失,那就更好,例如,当JS代码在网页中运行,同时你想保持与DOM元素的联系,在该元素可能被其他脚本移除的情况下,你应当不希望自己的代码留对该DOM元素的最后一个引用。
为了缓解这个问题,ES6也包含了Weak Set,该类型只允许存储对象弱引用,而不能存储基本类型的值。对象的弱引用在它自己成为该对象的唯一引用时,不会阻止垃圾回收。
创建Weak Set
Weak Set使用 WeakSet 构造器来创建,并包含*add()*方法、*has()方法以及delete()*方法。
let set = new WeakSet(),
key = {};
//将对象加入set
set.add(key);
console.log(set.has(key)); //true
set.delete(key);
console.log(set.has(key)); //false
使用Weak Set很像在使用正规的Set。你可以在Weak Set添加、移除或检查引用,也可以给构造器传入一个可迭代对象来初始化Weak Set的值:
let key1 = {},
key2 = {},
set = new WeakSet([key1,key2]);
console.log(set.has(key1)); //true
console.log(set.has(key2)); //true
在本例中,一个数组被传给了WeakSet 构造器。由于该数组包含了两个对象,这些对象就被添加到了Weak Set中。要记住若数组中包含了非对象的值,就会抛出错误,因为WeakSet构造器不接受基本类型的值。
Set 类型之间的关键差异
Weak Set与正规Set之间最大的区别是对象的弱引用。此处有个例子说明了这种差异:
let set = new WeakSet(),
key = {};
//将对象加入set
set.add(key);
console.log(set.has(key)); //true
//移除对于键的最后一个强引用,同时从Weak Set中移除
key = null;
当此代码被执行后,Weak Set中的key引用就不能再访问了。
差异:
- 1.对于WeakSet的实例,若调用add()方法时传入了非对象的参数,就会抛出错误(has()或delete()则会在传入了非对象的参数是返回false);
- 2.WeakSet不可迭代,因此不能被用在for-of 循环中;
- 3.WeakSet无法暴露出任何迭代器(例如keys()与values()方法),因此没有任何编程手段可用于判断WeakSet的内容;
- 4.WeakSet没有forEach()方法
- 5.WeakSet没有size属性
WeakSet看起来功能有限,而这对于正常管理内存而言是必要的。一般来说,若只想追踪对象的引用,应当使用WeakSet而不是正规Set。