es6入门7--Set Map数据结构
es6入门7–Set Map数据结构
本文作为ES6入门第十三章的学习整理笔记,可能会包含少部分个人的理解推测,若想阅读更详细的介绍,还请阅读原文ES6入门
一、set数据结构
1.set不接受重复值
ES6新增了Set构造函数用于创建set数据结构,这种结构类似于数组,但有很大的一个区别就是,set数据结构不接受重复值,每个值都是唯一的。
我们可以通过Set构造函数快速创建一个set数据结构,顺便打印看看究竟长什么样:
let s = new Set(); console.dir(s);
那么可以看到,set实例具有一个size属性,因为我们还未给此结构添加值,所以是0,类似于数组的length属性。
set实例还有很多方法,例如add添加,clear清除,还有在数组拓展中已经介绍过的keys,values等比较熟悉的方法,这些后面具体再说。
我们尝试在new命令时直接初始化值:
let s = new Set([1,2,1,3]);
可以看到,尽管我添加了两个数字1,最终的set实例结构中只有一个不重复的1,这是因为set不接受重复的值,自带去重效果。
你可能看过以下数组去重的快捷方法,正式利用的set的这一特点:
// 数组去重 [...new Set([1, 1, 2, 3, 4, 4])]; Array.from(new Set([1, 1, 2, 3, 4, 4]));
2.set实例的增删改查方法
add方法:添加某个值,返回添加值后的set解构,类似数组的push,后添加的元素在set解构后面。
let s = new Set(); s.add(1).add(2);
has方法:查找set解构是否包含某值,返回一个布尔值。
s.has(1); //true s.has(3); //false
delete方法:删除某个值,返回一个布尔值对应是否删除成功。
s.delete(1);//true s.delete(1);//false
clear方法:清除整个set解构,无返回值。
s.clear();
3.set的遍历方法
keys方法:遍历元素的键名
values方法:遍历元素的键值
entries方法:遍历元素的键值对
forEach方法:用的贼多,回调函数遍历每个元素
在数组拓展这一章节中也有介绍这三个方法,这里就简单说下;三个方法都是结合for…of循环使用,分别遍历元素的key,value与key/value组合。
let s = new Set([{a:1}, {b:2}, {c:3}]); for (let item of s.keys()) { console.log(item);// {a:1}, {b:2}, {c:3} }; for (let item of s.values()) { console.log(item);// {a:1}, {b:2}, {c:3} }; for (let item of s.entries()) { console.log(item);// [{a:1},{a:1}],[{b:2},{b:2}],[{c:3},{c:3}] };
通过上述代码中的输出可以了解到,keys方法与values方法执行完全相同,这是因为set解构没有key名导致,key名与value相同;而entries方法每次返回的是一个包含了key与value的数组。
当我们想遍历出set解构的每个元素理论上使用values方法,有趣的是set解构的默认遍历器刚好与values相等,所以我们甚至能省略掉values方法直接遍历解构中的每个元素。
let s = new Set([1, 2, 3]); Set.prototype[Symbol.iterator] === Set.prototype.values; //true //省略values方法 for(let item of s){ console.log(item);//1 2 3 };
与数组中使用这三个方法的区别在于,数组中的keys遍历的是元素的下标,values相同,entries是下标和元素组成键值对,且不是数组。
当我们使用forEach遍历set结构数据时,回调参数三个参数的前两个完全相同,这也是因为key名与key值相同的缘故,这点需要注意。
let s = new Set([1, 2, 3]); s.forEach((val,key) => console.log(val,key))//1 1,2 2,3 3
4.set解构的作用
a.数组去重,主要利用了set不接受重复值做参数的特点。
b.set结构实现并集,简单点说,就是把两个set重复项去掉,原理还是利用set不接受重复项
let a = new Set([1, 2, 3]); let b = new Set([2, 3, 4]); let s1 = new Set([...a, ...b]); //set {1,2,3,4}
c.set结构实现交集,原理是利用了set实例的has方法
let s2 = new Set([...a].filter(x => b.has(x)))//set {2,3}
d.set结构实现差集,同理利用了has方法
let s3 = new Set([...a].filter(x => !b.has(x)))//set {1}
你的直觉是不是这里应该是{1,4},这里的差集其实是a里面有且b里面没有的元素,而不是ab互相没有。
二、WeakSet结构
WeakSet数据结构与Set类似,也不接受重复的值,但也有三点不同,一是WeakSet解构的成员只能是对象,二是WeakSet中的对象都是弱引用,三是WeakSet无法遍历。
1.WeakSet成员只能是对象
let s = new WeakSet(); s.add([{a:1},{b:2}]); console.dir(s); s.add(1);//报错 Invalid value used in weak set
创建WeakSet 结构可通过new命令完成,WeakSet 接受任何含有Iterable接口的对象作为参数。可以看到当我们add非对象元素,该操作报错,但是add添加对象没问题。
那么我们看这段代码,为什么报错了:
let s = new WeakSet([1,2,3]);
我在前面你说了,WeakSet的每个成员必须是对象,前面我们使用的是add方法,每次添加都是一个成员,这是直接使用new初始化,虽然传递的参数是数组,但本质上等同于:
let s = new WeakSet(); s.add(1).add(2).add(3);
所以我们需要保证数组中的每个元素也是对象,这样就不会报错了:
let s = new WeakSet([{a:1},{b:2}]);
其次可以看到WeakSet方法并不多,add,has,delete三个,用法和set相同,这里就不重复介绍了。
2.WeakSet结构成员均为弱引用
我们都知道,当一个对象不被任何地方引用,垃圾回收机制就会释放掉这个对象所占用内存。我们在前面说WeakSet的成员都是对象,但是垃圾回收机制不考虑WeakSet的引用。
说直白点,现在对象a被A和WeakSet同时引用,A不再引用了垃圾回收机制就直接释放了,完全不管WeakSet还在引用它。
也正是因为WeakSet成员是弱引用的原因,我们无法保证什么时候成员就被释放了,所以WeakSet没有size属性,也不可遍历。
三、map数据结构
1.基本用法与增删改查方法
传统意义上的对象都是键值对组成的集合,键为字符串,值为一个对象,我们是无法使用对象作为键的。
但Map打破了这个规则,我们可以通过Map创建键值都是对象的数据结构,这样键不再是作为保存值的存在,在遍历时,键值都可以是有效的对象。
let m = new Map(); console.dir(m);
从上图中,可以看到百分之80的方法与Set数据结构完全相同,只是多了一个set方法和get方法。
set(key,value)方法:按照key/value添加成员,返回Map结构,支持链式写法;如果key已存在,则覆盖。
get(key)方法:按照key查找返回对应的value,如果未找到,返回undefined。
has(key)方法:查找是否包含某个key,返回一个布尔值。
delete(key)方法:删除对应的key,返回一个布尔值,表示是否成功删除。
clear()方法:清空整个Map数据结构。
let m = new Map(); let o = {name:'echo'}; m.set(o,{age:26}); m.get(o);//{age:26} m.has(o);//true m.delete(o);//true m.has(o);//false
那么在上述代码中,我们为map数据结构添加了一个key为{name:’echo’}值为{age:26}的成员。
同时我们可以通过get指定的key访问到对应的value,delete还是一样返回是否删除成功,has依旧是判断该数据结构是否含有此成员。
添加成员当然不要求通过set,在new命令执行时,我们可以以一个数组的形式传递需要添加的成员。
let m = new Map([ ['name', '听风是风'], ['age', 26] ]); m.has('name') //true m.get('name') //听风是风 m.has('age') //true m.get('age') //26
其实初次看到这我是有点懵逼的,为什么我一个数组成员的两个元素,成了Map数据结构中一个成员的key与value。其实这个不难理解,它等同于以下的执行:
let arr = [ ['name', '听风是风'], ['age', 26] ]; let m = new Map(); arr.forEach(([key, value]) => m.set(key, value))
数组每个元素又是一个双元素数组,前者作为map的key,后者作为map的value
需要注意的是,map数据结构同样不接受重复的值作为成员,这里的重复是指key名相同,如果相同,后者会覆盖前者:
const m = new Map([ ['name', 1], ['name', 2] ]); console.log(m);//key:name value:2
除此之外,当我们map的key是对象时,需要注意对象引用的问题:
let o = {name:1}; let m = new Map(); m.set(o,2) console.log(m.get(o));//2 m.set({name:1},2) console.log(m.get({name:1}));//undefined
在上述代码中,如果我们直接将{name:1}作为key用于存值,在set执行时,无法拿到对应的value,这是因为对象尽管写法相同,但仍然是完全不同的两个东西;
所以在需要将对象做key时,请将此对象赋予一个变量,利用此变量作为key进行存储,在读取时再次读取这个变量,就可以避免这个问题了。
其实说到这里,关于map的key,其实是跟内存地址相关。如果key是一个简单数据类型,那么只要两个key完全相等,就视为一个key,且后者覆盖前者,如果不相等,则反之。
如果key是一个对象,想正确的存取,请将对象赋予给一个变量再做set操作。否则会因为引用地址问题无法访问到你已经添加的key。
2.Map数据结构的遍历方法
keys()方法:遍历并返回键名
let m = new Map([ ['name', '听风是风'], ['age', 26] ]); for(let key of m.keys()){ console.log(key);//name age };
values()方法:遍历并返回键值
for(let value of m.values()){ console.log(value);//听风是风 26 };
entries()方法:遍历返回所有成员,注意,我没说这里是返回键值对
for(let item of m.entries()){ console.log(item); };
如上图,返回两个数组,每个数组分别包含了key和value,所以如果我们想直接访问key,value,应该这么写:
for(let [key,value] of m.entries()){ console.log(key,value);//name 听风是风,age 26 };
还记得在介绍Set数据结构是,我们说Set的默认遍历器接口等于values方法,所以我们可以简写遍历,比较好运的是,Map数据结构的默认遍历器接口等于entries方法,所以我们还可以继续简写:
m[Symbol.iterator] === m.entries; //true for (let [key, value] of m) { console.log(key, value);//name 听风是风,age 26 };
forEach方法,通过回调参数也可以方便的访问到Map结构的key与value
m.forEach((value, key, m) => console.log(value, key));//听风是风 name,26 "age"
四、WeakMap数据结构
WeakMap与Map结构类似,但也有两点不同,一是WeakMap成员的key只接受对象:
let m = new WeakMap(); m.set('name',1);//报错
二是WeakMap的键名所引用对象为弱引用,也就是不计入垃圾回收机制,这点与WeakSet一致。
let m = new WeakMap(); let ele = document.querySelector("#div"); m.set(ele, '这是一个div元素'); m.get(ele); //这是一个div元素
在上述代码中,我们先是将获取的dom存在了ele变量中,此时对于div dom的引用次数是1次。
然后我们又将ele作为key,为这个ele添加了一些说明,照理说,div dom此时又被WeakMap结构引用,所以div引用次数是2次。
但由于WeakMap的key名对象是弱引用,所以这里div一共的引用此事还是1次。当我们让ele不再引用div元素时,垃圾回收机制不会考虑WeakMap对于div的引用,而是直接释放,这点其实与WeakSet是保持一致的。
强调一点的是,WeakMap弱引用的是key,而不是value,这里有个例子:
let m = new WeakMap(); let key = {}; let value = {a: 1}; m.set(key, value); value = null; m.get(key) //{a:1}
即便我们将WeakMap中value所引用的对象释放,其实垃圾回收机制还是将WeakMap的引用计为1次,所以还能正常读取到。
因为key是弱引用的缘故,所以与WeakSet一样,不存在遍历方法。
WeakMap结构最大的一个用处就是用于保存dom,这样dom元素被删除也不会造成内存泄漏问题:
let ele = document.getElementById('logo'); let fn = function () { console.log(1) }; let m = new WeakMap(); //将dom元素与需要执行的函数作为WeakMap结构的key与value m.set(ele, fn); //为dom元素增加监听 ele.addEventListener('click', function () { //执行监听函数 m.get(ele)(); }, false);
关于WeakMap这里就不多做介绍了,至少我目前开发基本使用不到…..
不只是是WeakMap,Set与Map的使用概率基本很低,这里就纯做一个整理了,日后万一用到,或者说使用逐渐普及,也方便查找。
那么就写到这里了。