ES6新增了数据结构Set和Map来提供更加强大的集合功能,同时新增了Iterator遍历器接口为各种表示”集合”的数据结构提供了统一的遍历结构,很大程度上方便了“集合”操作。
一、Set数据结构
Set是ES6提供的一种新的类似于数组的数据结构,其成员唯一,没有重复的值。总体而言与Java中的Set集合有一定程度的类似。
1.Set的构造
Set本身是一个构造函数,用来生成Set数据结构,其可以接受一个数组或者类似数组的对象作为参数用来初始化。代码示例:
//Set结构不会添加重复的值
var s = new Set();
[2,3,5,4,5,2,2].map(x => s.add(x));
for (i of s){console.log(i);}//2 3 4 5
var set = new Set([1,2,3,3]);
[...set];//[1,2,3]
注意两点:
- 向Set加入值是不会发生类型转换,因此5和’5’是两个不同的值
- Set内部判读两个值是否相等和精确相等运算符===基本一致,唯一例外是NaN,Set认为NaN等于自身,而===不等于自身
2.Set实例的属性与操作方法
Set实例有以下属性:
- Set.prototype.constructor:构造函数,默认就是Set()函数
- Set.prototype.size: 返回Set实例的成员总数
Set实例的方法分为两大类:操作方法和遍历方法。操作方法用于操作数据,方法有:
- add(value): 添加某个值,返回Set结构本身
- delete(value): 删除某个值,返回一个布尔值表示删除是否成功
- has(value): 返回一个布尔值,表示参数是否为Set的成员
- clear(): 清除所有成员, 没有返回值
Array.from方法可以将Set结构转为数组,这样间接提供了一种去除数组的重复元素的方法:
function dedupe(array){
return Array.from(new Set(array));
}
3.Set的遍历方法
Set有四个遍历方法用来遍历成员:
- keys(): 返回一个键名的遍历器
- values(): 返回一个键值的遍历器。因为Set没有键名只有键值,因此keys()和values()的行为一致。
- entries(): 返回一个键值对的遍历器。
- forEach(): 使用回调函数遍历每个成员
let set = new Set(['aa', 'bb', 'cc']);
//keys
for(let item of set.keys()){
console.log(item); // aa bb cc
}
//values
for(let item of set.values()){
console.log(item); // aa bb cc
}
//entries
for(let item of set.entries()){
console.log(item); // ['aa','aa'] ['bb','bb'] ['cc', 'cc']
}
Set默认的遍历器生成函数是它的values方法,因此我们可以省略values方法直接用for…of遍历Set.另外扩展运算符…内部使用for…of遍历因此也可以使用…直接遍历数组,这也是一种便捷的去除数组重复元素的方法。
let set = new Set(['aa', 'bb', 'cc']);
for(let item of set){
console.log(item); //aa bb cc
}
let arr = [3,5,2,2,5,5];
let unique = [...new Set(arr)];
unique;//[3, 5, 2]
数组的map和filter方法可以用于Set.通过Set可以很容易的实现并集、交集和差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
//并集
let union = new Set([...a, ...b]);//[1, 2, 3, 4]
//交集
let intersect = new Set([...a].filter(x => b.has(x)));//[2, 3]
//差集
let difference = new Set([...a].filter(x => !b.has(x)));//[1]
//forEach方法用于对每个成员执行某种操作,没有返回值
let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2));//2 4 6å
如果想在遍历操作中同步改变原来的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
4.WeakSet
WeakSet与Set类似也是不重复的值的集合,但与有两个区别:
- 成员只能是对象而不能是其他类型的值
- WeakSet中的对象都是弱引用。因此如果失去引用那么垃圾回收器会自动回收该对象占得内存而不会考虑该对象还存在于WeakSet中,因此我们无法引用WeakSet的成员,因此WeakSet不可遍历
WeakSet的构造很Set类似,是通过WeakSet()函数,也可以传递一个数组或类似数组的对象作为参数(任何具有iterator接口的对象都可以作为WeakSet的参数。
WeakSet有下面三个方法:
- add(value): 向WeakSet添加一个成员
- delete(value): 清除WeakSet实例的指定成员
- has(value): 返回布尔值表示某个值是否在WeakSet实例中
WeakSet没有size属性,无法遍历其成员。它的一个用处是储存DOM节点而不用担心这些节点从文档移除时会引发内存泄露。
二、Map
Map是ES6提供的数据结构,类似于对象也是键值对的集合,但是和对象的键只能是字符串不同,Map可以把各种类型的值当作键。如果需要键值对的数据结构,Map比Object更合适
1.Map的构造
Map使用Map()函数进行构造,该函数可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组。
var m = new Map();
var o = {p: "Hello World"};
m.set(o, 'content');
m.get(o);//'content'
几点注意:
- 如果对同一个键多次赋值,后面的值将覆盖前面的值
- 如果读取一个未知的键, 则返回undefined
- 对于对象作为键时,只有对同一个对象的引用Map结构才将其视为同一个值,Map的键是跟内存地址绑定的,只要内存地址不一样就视为两个键
2.Map实例的属性与方法
Map结构的实例有如下属性和操作方法:
- size属性: 返回Ma结构的成员总数
- set(key, value): 设置键值然后返回整个Map结构,如果key已经有值则键值会被覆盖更新,否则就新生成该键
- get(key): 获取key对应的键值,如果找不到key则返回undefined
- has(key): 返回一个布尔值表示某个键是否在Map数据结构中
- delect(key): 删除某个键返回true,如果删除失败则返回false
- clear(): 清除所有的成员没有返回值
3.Map的遍历
Map提供了3个遍历器生成函数和1个遍历方法:
- keys(): 返回键名的遍历器
- values(): 返回键值的遍历器
- entries(): 返回所有成员的遍历器。Map结构默认的遍历器接口就是entries方法。
- forEach(): 遍历Map的所有成员
let map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for(let item of map.entries()){
console.log(item[0], item[1]);//'F' 'no', 'T' 'yes'
}
for(let [key, value] of map.entries()){
console.log(key, value);//'F' 'no', 'T' 'yes'
}
4.Map与其他数据结构的转换
Map与数组互相转换
Map转为数组最简单的方法就是使用扩展运算符,而将数组作为参数传入Map函数就可以将数组转为Map.
对象与Map互相转换
如果Map的所有键值都是字符串则其可以转为对象。代码示例:
function mapToObj(map){
let obj = Object.create(null);
for(let [k, v] of map){
obj[k] = v;
}
return obj;
}
let map = new Map.set('yes', true).set('no', false);
mapToObj(map);//{yes:true, no:false}
function objToMap(obj){
let map = new Map();
for(let k of Object.keys(obj)){
map.set(k, obj[k]);
}
return map;
}
objToMap({yes:true, no:false});//[['yes':true], ['no': true]]
Map与JSON相互转换
Map转为JSON有两种情况,一种是Map键名都是字符串,这时可以选择转为对象JSON,如果有非字符串,可以转为数组JSON
//转为对象JSON
function mapToJson(map){
return JSON.stringify(mapToObj(map));
}
//转为数组JSON
function mapToArrayJson(map){
return JSON.stringify([...map]);
}
//JSON转为Map,正常情况下所有键名都是字符串
function jsonToMap(jsonStr){
return objToMap(JSON.parse(jsonStr));
}
//有一种特殊情况就是整个JSON是一个数组,而每个数组成员都是一个有两个成员的数组,它可以一一对应的转为Map
5.WeakMap
WeakMap数据结构和Map结构类似,唯一区别是只接受对象作为键名(null)除外,而且键名所指向的对象不计入垃圾回收机制。
WeakMap的设计目的在于:
键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能被自动回收,当对象被回收后,WeakMap自动移除对应的键值对。典型应用是一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其对应的WeakMap记录就会被自动清除.基本上WeakMap的专用场合就是它的键对应的对象可能在将来消失。该结构有利于防止内存泄露。其另一个用处是部署私有属性
其与Map有两个区别:
- 没有遍历操作也没有size属性
- 无法清空,不支持clear()方法。其只有四个方法get(),set(),has(),delete()
三、Iterator
1.Iterator接口简介
Iterator可以理解为我们传统上的遍历器。ES6中的‘集合’数据结构共有:数据、对象、Set和Map四种。Iterator为它们提供了统一的遍历接口。
Iterator有三个作用:
- 为上面说的四种数据结构提供统一、简便的访问接口
- 使得数据结构的成员可以按照某种次序进行排列
- 提供了新的遍历方式:for of循环
ES6中默认的Iterator接口部署在数据结构的Symbol.iterator属性上。一个数据结构只要具有Symbol.iterator属性就可以认为是可遍历的,可以被for…of循环所遍历。该属性本身是一个表达式,返回Symbol对象的iterator属性。
Iterator遍历过程为:
- 1.创建一个指针对象指向当前数据结构的起始位置
- 2.调用指针对象的next方法可以返回数据结构的成员
- 3.不断调用next方法直到指针指向数据结构的结束位置
调用next方法返回的是一个包含value和done两个属性的对象,value属性就是当前成员的值,done属性是一个布尔值用来表示遍历是否结束。
var iter = makeIterator(['a','b']);
iter.next();//{value:'a', done:false}
iter.next();//{value:'b', done:false}
iter.next();//{value:undefined, done:true}
在ES6的原生数据结构中,数组、Set、Map以及某些类似数组的对象天生具备Iterator接,他们部署了Symbol.iterator属性。然后对象并不具备Iterator对象,因为本质上for…of是线性遍历,对象无法确定哪个属性先遍历,哪个属性后遍历。因为必须我们手动添加指定方法,将原来非线性的属性遍历转换为线性遍历。
下面是《ES6标准入门》中的两个为对象添加iterator的例子:
//示例1
let obj = {
data: ['hello world'],
[Symbol.iterator](){
const self = this;
let index = 0;
return {
next(){
if(index < self.data.length){
return {
value: self.data[index ++],
done:false
};
}else{
return {value:undefined, done:true};
}
}
};
}
};
//类似数组的对象可以直接引用数组的Iterator接口
let iterable = {
0:'a',
1:'b',
2:'c',
length:3,
[Symbol.iterator]:Array.prototype[Sysbol.iterator]
}
for(let item of iterable){
console.log(item);//a,b,c
}
2.for…of循环
只要部署了Symbol.iterator属性就视为具有Iterator接口,可以被for…of循环遍历成员。for…of循环内部调用的就是数据结构的Symbol.iterator方法。其使用范围包括:Set和Map结构,某些类似数组的对象,Generator对象以及字符串。
下面是相关示例代码
//遍历数组
const arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
//下面两者等价
for(let v of arr){ console.log(v)};// a b c
for(let v of iterator){ console.log(v)};//a b c
//遍历Set与Map
//两点注意:
//1.遍历的顺序是按照各个成员的添加顺序
//2.遍历Set返回的是一个值,Map返回的是一个数组,该数组的两个成员为当前Map成员的键名和键值
var setdemo = new Set(['a', 'b', 'c']);
for(let v of setdemo){
console.log(v)//a b c
}
var mapdemo = new Map();
map.set(1, 'a');
map.set(2, 'b');
map.set(3, 'c');
for(var [name, value] of mapdemo){
console.log(name+":"+value)
}
//字符串的遍历
let str = 'hello';
for(let s of str){
console.log(s);//h e l l o
}