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

    那么就写到这里了。

  • 相关阅读:
    设置开发环境
    安装开发软件
    学习路线
    预备知识
    Spring是什么
    yum安装nginx
    .net 哈希
    Excel文件处理Demo
    汉字处理组件
    Log4Net
  • 原文地址:https://www.cnblogs.com/echolun/p/11001138.html
Copyright © 2011-2022 走看看