Set集合是一种无重复元素大的列表,开发者一般不会逐一读取数组中的元素,也不太可能逐一访问Set集合的每一个元素,通常是检测所给元素是否在集合中存在。Map集合内含多组键值对,经常用于缓存频繁取用的数据。
ECMAScript5中的Set和Map集合
在ES5中,常用对象属性来模拟这两种集合。
var set=Object.create(null);
set.foo=true;
//检查属性是否存在
if(set.foo){
//要执行的代码
}
这里的set是个原型为null的对象,不继承任何属性。在ES5中,开发者们用类似方法检查某个对象的属性值是否存在。
模拟Set和Map这两种集合对象的唯一区别是存储的值不同。
var map=Object.create(null);
map.foo="bar";
var value=map.foo;
console.log(value);//"bar"
这段代码将字符串"bar"存储在map.foo中。一般来说,Set集合常被用于检查对象中是否存在某个键名,而Map集合常被用来获取已存得信息。
该解决方案的一些问题
碰到对象属性的某些限制,上诉方法就会复杂。例如:所有对象的属性名必须是字符串类型,必须保证每个键名都是字符串类型且在对象中是唯一的。
var map=Object.create(null);
map[5]="foo";
console.log(map["5"]);//"foo"
例子中属性键名5被强制转换为了"5",如果想以数字为对象键名就会出问题了。
var map=Object.create(null),key1={},key2={};
map[key1]="foo";
console.log(map[key2]);//"foo"
由于例子中key1和key2将被转换为对象对应的字符串都是"[Object Object]",所以map[key2]和map[key1]其实引用同一个属性。这种错误很难被发现。
对于Map集合,如果它的属性值是假值,则要求使用布尔值的情况下会自动转换为false。强制转换在某些场景会出错.
var map=Object.create(null);
map.count=0;
if(map.count){
//要执行的代码
}
在这里如果我们只是检查count值是否存在,则会出错,因为0虽然存在,但是会被转化为false,则导致出错。
在大型软件应用中,一旦发生这种问题将难以定位和调试.
ECMAScript6中的Set集合
ECMAScript中新增的Set类型是一种有序列表,其中含有一些相互独立的非重复值,通过Set集合可以快速访问里面的数据,更有效追踪各种离散值。
创建Set集合并添加元素
let set=new Set;//创建Set集合
set.add(5);//添加元素
set.add("5");//添加元素
console.log(set.size);//2,size属性可以返回当前的元素数量。
在Set集合中,不对所存值发生强制的类型转换。数字5和字符"5"是独立存在的(引擎调用Object.is()判断)。
let set=new Set(),key1={},key2={};
set.add(key1);
set.add(key2);
console.log(set.size);//2
多次调用add()方法来传入相同的值作为参数,后续的调用会被忽略。
let set=new Set;
set.add(5);
set.add("5");
set.add(5);
console.log(set.size);//2
Set函数可以用数组来初始化初始化,但是重读元素会被过滤掉,保证集合元素的唯一性。
let set=new Set([1,2,3,4,5,5,5,5,5]);
console.log(set.size);//5
实际上Set构造函数可以接受所有的迭代对象作为参数。
通过has()方法可以检测Set集合中是否含有某个值。
let set=new Set;
set.add(5);
set.add("5");
console.log(set.has(5));//true
console.log(set.has("5"));//true
console.log(set.has(6));//false,没有的元素会是false
移除元素
通过delete()可以移除Set集合中的某一元素,调用clear()方法会移除所有元素。
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.claer();
console.log(set.has("5"));//false
console.log(set.size);//0
Set集合中的forEach()方法
forEach()方法的回调函数接受三个参数:
·Set集合中下一次索引的位置
·与第一个参数一样的值
·被遍历的Set集合本身
let set=new Set([1,2]);
set.forEach(function(value,key,ownerSet) {
console.log(key+" "+value);
console.log(ownerSet===set);
});
//1 1
//true
//2 2
true
令人疑惑的是Set的这个方法中前两个参数都是一样的,其实这也可以解释得通,因为Set没有键名,但是设为2个参数,就和Map集合和数组的forEach方法区别太大了,所以统一为三个参数。
let set=new Set([1,2]);
let processor={
output(value){
console.log(value);
},
process(dataSet){
dataSet.forEach(function(value){
this.output(value);
},this);
}
};
processor.process(set);
在forEach方法中,第二个参数也与数组的一样,如果需要在回调中使用this调用,则可以将它作为第二个参数传入forEach()函数;
//箭头函数重写
let set=new Set([1,2]);
let processor={
output(value){
console.log(value);
},
process(dataSet){
dataSet.forEach(value=>
this.output(value));
}
};
processor.process(set);
值得注意的是,尽管Set集合更适合用来追踪多个值,而且又可以通过forEach()方法操作每个参数,但是你不能像访问数组那样直接通过索引访问集合中的元素。如果有需要,可以转化为一个数组。
将Set集合转换为数组
数组转换为Set集合很简单,只要把数组传入Set构造函数就可以了,转换回去同样很简单,使用(...)运算符就可以了。
let set=new Set([1,2,3,3,3,4,5]),array=[...set];
console.log(array);//[1,2,3,4,5]
如果想要使数组变为无重复元素的数组,用这个方法就很简单了。
let test=[1,2,2,3,3,6];
(function eliminateDuplicates(items){
return [...new Set(items)];
})(test)//[1,2,3,6]
Weak Set集合
将对象存储在Set的实例与存储在变量中完全一样,只要Set实例中的引用存在,垃圾回收机制就不能释放该对象的内存空间,于是之前提到的Set类型可以被看做是强引用的Set集合。
let set=new Set(),
key={};
set.add(key);
console.log(set.size);//1
//移除原始引用
key=null;
console.log(set.size);//1
key=[...set][0];
Set集合会保留原始引用,这容易导致内存泄漏,ES6中引入了另外的一个类型:WeakSet集合。
创建Weak Set集合
//Weak Set集合支持三个方法: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
两种类型的主要区别
let set=new WeakSet(),\
key={};
set.add(key);
console.log(set.has(key));//true
key=null;//移除了
因为has()方法要传递强用,所以接下来无法验证了,但是JavaScript引擎会正确移除最后一个弱引用。
还有其它的差别:
1.在Weak Set的实例中,如果向add()、has()、delete()传入非对象参数会导致程序报错。
2.Weak Set集合不可迭代,不能用for-of循环。
3.Weak Set集合不暴露任何迭代器,所以无法通过程序自身来检测其中内容。
4.Weak Set集合不支持forEach()方法。
5.Weak Set集合不支持size属性。
ECMAScript中的Map集合
ECMAScript6中的Map类型是一种存储着许多键值对的有序列表。其中键名和对应的值都支持所有的数据类型。键名的等价性判断是通过调用Object.js()方法实现的。
如果需要添加新元素,可以使用set()方法,分别传入键名和对应值作为两个参数:如果要从集合中获取信息,并调用get()方法。
let map=new Map();
map.set("title","Understanding ECMAScript");
map.set("year",2016);
console.log(map.get("title"));//"Understanding ECMAScript"
console.log(map.get("year"));//2016
console.log(map.get("CCG"));//undefined
可以使用对象作为对象属性的键名。
let map=new Map(),
key1={},
key2={};
map.set(key1,5);
map.set(key2,42);
console.log(map.get(key1));//5
console.log(map.get(key2));//42
Map集合支持的方法
1.has(key)检测键名是否存在。
2.delete(key)移除键名和对应值。
3.clear()移除Map集合中所有的键值对。
let map=new Map();
map.set("name","Nicholas");
map.set("age",25);
console.log(map.size);//2
console.log(map.has("name"));//true
console.log(map.get("name"));//"Nicholas"
console.log(map.has("age"));//true
console.log(map.get("age"));//25
map.delete("name");
console.log(map.has("name"));//false
console.log(map.get("name"));//undefined
console.log(map.size);//1
map.clear();
console.log(map.has("name"));//false
console.log(map.get("name"));//undefined
console.log(map.has("age"));//false
console.log(map.get("age"));//undefined
console.log(map.size);//0
Map集合初始化方法
可以向Map构造函数传入数组来初始化一个Map集合,数组中的每个元素都是一个子数组,子数组包含一个键值对的键名与值两个元素。
let map=new Map([["name","Nicholas"],["age",25]]);
console.log(map.has("name"));//true
console.log(map.get("name"));//"Nicholas"
console.log(map.has("age"));//true
console.log(map.get("age"));//25
console.log(map.size);//2
Map集合的forEach()方法
1.Map集合的下一次索引的位置
2.值对应的键名
3.Map集合本身
let map=new Map([["name","Nicholas"],["age",25]]);
map.forEach(function (value,key,ownerMap) {
console.log(key+" "+value);
console.log(ownerMap===Map);
});
//name Nicholas
//true
//age 25
//true
Weak Map集合
Weak Map是弱引用的Map集合,也用于存储对象的弱引用。Weak Map集合中的键名必须是一个对象,如果使用非对象键名会报错;集合中保存的是这些对象的弱引用,如果在弱引用之外不存在其他强引用,就会被自动回收,同事也会移除Weak Map集合的键值对。但是只有集合中的键名遵从这个规则,键名对应的值如果是一个对象,则保存对象的强引用,不会触发垃圾收集。
Weak Map集合最大的用途就是保存Web页面的DOM元素。
使用这个这种方法的困难是:一旦清楚元素,如何通过库本身将对象清除。但是用Weak Map集合来跟踪DOM元素,这些库仍可以通过自定义对象整合,而且当DOM元素消失时,可以自动销毁相关对象。
使用Weak Map集合(这部分引用阮一峰老师的博客:http://es6.ruanyifeng.com/#docs/set-map#WeakMap)
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。
也就是说,上面的 DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。
总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
上面代码中,键值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 内部的引用依然存在。
Weak Map集合支持的方法
1.has()检测给定的键值在集合中是否存在。
2.delete()移除制定的兼职对。
3.set()写入值。
4.get()得到值。
let map=new WeakMap(),
element=document.querySelector(".element");
map.set(element,"Original");
console.log(map.has(element));//true
console.log(map.get(element));//"Original"
map.delete(element);
console.log(map.has(element));//false
console.log(map.get(element));//undefined
Weak Map集合中的使用方法和使用限制
1.如果只用对象作为对象的键名,Weak Map是最好的选择。
2.如果需要forEach()属性,size属性和clear()方法来管理集合中的元素,那么Map集合是一个更好的选择,只是要注意内存的使用情况。