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的使用概率基本很低,这里就纯做一个整理了,日后万一用到,或者说使用逐渐普及,也方便查找。

那么就写到这里了。

posted on 2019-06-17 23:15 听风是风 阅读() 评论() 编辑 收藏

版权声明:本文为echolun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/echolun/p/11001138.html