美文网首页技术文档
ES6—Map和Set数据结构

ES6—Map和Set数据结构

作者: 林之小记 | 来源:发表于2019-10-25 16:05 被阅读0次

    作者:米书林
    参考文章:《菜鸟教程》、《 ECMAScript 6 入门》(阮一峰)

    Map对象

    Map对象简介

    作用:Map对象用于保存键值对,它的键可以为任意类型的数据,常用于建立数据的映射关系。

    Map对象和Object对象的对比

    相同点:

    -它们都是对象数据类型
    -它们都是以键值对的形式存储数据

    不同点:

    -Object对象的键只能是字符串或Symbol,Map对象的键可以是任意数据类型(可以看作是值-值的关系)
    -Map 中的键值是有序的(FIFO 原则),而添加到Object对象中的键则不是
    -Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算
    -Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突

    Map 中的 key

    key是字符串

    用法:
    Map()是一个构造函数,需要用new来实例化,Map()构造函数貌似只能接收一个二维数组的参数null和undefined(至少目前遇到的是这样),其他类型参数会报错

    // Map()可以接收一个二维数组,二维数组中取arr[][0]为key,arr[][2]为value,多余的数组元素会被忽略
    let map = new Map([[1,20]]);
    // Map(1) {1 => 20}
    let map1 = new Map([[1,20,30]]);
    // Map(1) {1 => 20}
    
    // Map()可以接收null作为参数
    let map2 = new Map(null);  // Map(0) {}
    
    // Map()可以接收undefined作为参数
    let map3 = new Map(undefined);  // Map(0) {}
    

    注意:null和undefined作为参数时相当于不传参数,即直接new Map()

    let smap = new Map();
    // set设置Map值
    smap.set("one",1);
    // get获取Map键对应的值
    smap.get("one");
    

    应用场景:
    1.处理英文缩写和汉语对应关系,下面以星期为例

    function eToC(str){
      // 实例化一个Map
      let wmap = new Map();
      // 使用实例化的Map存储具有对应关系的数据
      wmap.set("Mon","星期一");
      wmap.set("Tue","星期二");
      wmap.set("Wed","星期三");
      wmap.set("Thur","星期四");
      wmap.set("Fri","星期五");
      wmap.set("Sat","星期六");
      wmap.set("Sun","星期日");
      // 根据查询字符串,返回对应数据
      return wmap.get(str);
    }
    
    // 直接调用
    eToC("Thur");   // "星期四"
    // 获取当前时间后调用
    let nowDate = (new Date()).toString();
    let end = nowDate.indexOf(" ");
    let week = nowDate.slice(0,end);
    eToC(week);    // "星期三"
    

    上面的方法来获取星期显然很笨拙,因为我们需要处理字符串,实际上我们可以通过getDay()获取到一个0~6的数字,然后再通过Map来对应,具体方法见key为数字小节。

    2.模拟简单的自动回复机器人
    因为对话机器人我们不需要作特别复杂的逻辑处理,以为都是使用判断添加来处理,但现在来看,好像Map结构数据更适合,我们还是直接来看代码吧。

    // 呆板机器人回复
    function ack(str){
      let ack_map = new Map();
      ack_map.set("你好","你好啊!");
      ack_map.set("今天天气真不错","是啊,很适合出门!");
      ack_map.set("吃饭了没","还没呢,你呢?");
      ack_map.set("吃啦","我还有点事,先走了,拜拜!!");
      ack_map.set("没吃呢","那赶快去吃吧,我还有点事,先走了,拜拜!!");
      // 能匹配到就返回
      if(ack_map.get(str)) return ack_map.get(str);
      // 匹配不到就返回一个提示
      else return "对不起,你说的话我听不懂"
    }
    

    上面代码只要调用函数就能简单的返回一些数据了,当然啦,工作上的自动回复机器人这么笨是会被老板开除的,我们要做一个稍微复杂机器人是少不了正则匹配和数组的,这个可以自己去探究。

    key是对象
    var myMap = new Map();
    var keyObj = {}, 
     
    myMap.set(keyObj, "和键 keyObj 关联的值");
    
    myMap.get(keyObj); // "和键 keyObj 关联的值"
    myMap.get({}); // undefined, 因为 keyObj !== {}
    

    key 是函数

    var myMap = new Map();
    var keyFunc = function () {}, // 函数
     
    myMap.set(keyFunc, "和键 keyFunc 关联的值");
     
    myMap.get(keyFunc); // "和键 keyFunc 关联的值"
    myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}
    

    key 是 NaN

    var myMap = new Map();
    myMap.set(NaN, "not a number");
     
    myMap.get(NaN); // "not a number"
     
    var otherNaN = Number("foo");
    myMap.get(otherNaN); // "not a number"
    

    虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。

    key 是 数组

    let map = new Map([
      ['name', '张三'],
      ['age', 23],
    ]);
    
    map.size // 2
    map.has('name') // true
    map.get('name') // "张三"
    map.has('age') // true
    map.get('age') // 23
    

    Map的key可以是任何类型的值,包括正则等;

    要点

    1.Map的key可以为任意类型的值;
    2.new一个Map就是新实例化了一个Map;
    3.对同一个key值进行多次赋值,后面的会覆盖前面的;
    4.未被set的Map关键字使用get方法会返回undefined;
    5.Map 的键是跟内存地址绑定的,引用类型数据需要指向同一个地址才是同一个键;
    6.原始数据类型,只要他们的值严格相等,Map 将其视为一个键;
    7.NaN不严格相等于自身,但 Map 将其视为同一个键;
    8.undefined和null是两个不同的键

    // new一个Map就是新实例化了一个Map
    let myMap1 = new Map();
    let myMap2 = new Map();
    myMap1 === myMap2 ;  // false
    
    // 对同一个key值进行多次赋值,后面的会覆盖前面的;
    let myMap = new Map();
    myMap.set("a", 'a')
    myMap.get("a") // "a"
    myMap.set("a", 'aa');
    myMap.get("a") // "aa"
    
    // 未被set的Map关键字使用get方法会返回undefined;
    let myMap3 = new Map();
    myMap3.get("b");  // undefined
    
    // Map 的键是跟内存地址绑定的,引用类型数据需要指向同一个地址才是同一个键
    let myMap4 = new Map();
    myMap4 .set(['12'], 12);
    myMap4 .get(['12']) // undefined
    myMap4 .set({name:"abc"}, "abc");
    myMap4 .get({name:"abc"}) // undefined
    let obj = {name:"abc"}
    myMap4 .set(obj , "abc");
    myMap4 .get(obj) // "abc"
    
    // 原始数据类型,只要他们的值严格相等,Map 将其视为一个键;
    let map = new Map();
    map.set(-0, 123);
    map.get(+0) // 123
    
    map.set(true, 1);
    map.set('true', 2);
    map.get(true) // 1
    // undefined和null是两个不同的键
    map.set(undefined, 3);
    map.set(null, 4);
    map.get(undefined) // 3
    // NaN不严格相等于自身,但 Map 将其视为同一个键;
    map.set(NaN, 123);
    map.get(NaN) // 123
    // 
    
    Map的常用方法

    1.set()
    set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    

    注意:set方法返回的是当前的Map对象,因此可以采用链式写法。

    2.get()
    get方法读取key对应的键值,如果找不到key,返回undefined

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.get(90); // "优秀"
    myMap.get(59); // undefined
    

    3.has()
    has方法判断某个键是否在当前 Map 对象之中,返回一个布尔值。

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.has(90); // true
    myMap.has(30); // false
    

    4.delete()
    delete方法用于删除某个键,删除成功返回true,删除失败(不存在的键)返回false。

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.delete(90);  // true
    myMap.has(90);  // false
    myMap.delete(90);  // false
    

    5.clear()
    clear方法清除所有成员

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.has(60);   // true
    myMap.clear();  
    myMap.has(60);  // false
    
    Map的属性

    1.size
    size属性返回 Map 结构的成员总数

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.size;   // 4
    

    Map的遍历(迭代)

    for...of

    Map 结构原生提供三个遍历器生成函数:
    -Map.prototype.keys():返回键名的遍历器。
    -Map.prototype.values():返回键值的遍历器。
    -Map.prototype.entries():返回所有成员的遍历器。

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
     // 遍历myMap
    for (var [key, value] of myMap) {
      console.log(key + " = " + value);
    }
    // 结果:
    //   60 = 及格
    //   80 = 良
    //   90 = 优秀
    //   100 = 非常优秀
    
    for (var [key, value] of myMap.entries()) {
      console.log(key + " = " + value);
    }
    /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
    // 结果:
    //   60 = 及格
    //   80 = 良
    //   90 = 优秀
    //   100 = 非常优秀
     
    for (var key of myMap.keys()) {
      console.log(key);
    }
    /* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */
    // 结果:
    //   60
    //   80
    //   90 
    //   100 
     
    for (var value of myMap.values()) {
      console.log(value);
    }
    /* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */
    // 结果:
    //   及格
    //   良
    //   优秀
    //   非常优秀
    
    forEach()
    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    myMap.forEach(function(value, key) {
      console.log(key + " = " + value);
    }, myMap)
    // 执行结果
    // 60 = 及格
    // 80 = 良
    // 90 = 优秀
    //100 = 非常优秀
    

    Map 结构的操作

    1.Map 转为数组

    Map结构可以通过扩展运算符(...)转为数组

    let myMap = new Map()
      .set(60, '及格')
      .set(80, '良')
      .set(90, '优秀')
      .set(100, '非常优秀');
    [...myMap] 
    // 返回:
    [[60, "及格"],[80, "良"],[90, "优秀"],[100, "非常优秀"]]
    

    返回的是一个二维数组,Map中的keyvalue被处理成数组的两个元素。

    Map 的克隆

    var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
    var myMap2 = new Map(myMap1);
    console.log(myMap1 === myMap2 ); 
    // 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。
    

    Map 的合并

    var myMap1  = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
    var myMap2 = new Map([[1, 'uno'], [2, 'dos']]);
     
    // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
    var myMap = new Map([...myMap1, ...myMap2]);
    console.log(myMap);
    // Map(3) {1 => "uno", 2 => "dos", 3 => "three"}
    

    Set结构

    Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

    Set要点

    -Set允许存储任何类型的唯一值,但Set()不能接收Object、Boolean和NaN,使用add()方法可添加任意类型值;
    -向 Set 加入值的时候,不会发生类型转换,所以123和"123"是两个不同的值;
    -Set 内部使用“===”来判断两个值是否相等,但是NaN是个特例
    -引用类型得地址指向相同才是唯一值,Symbol得用变量存储才能被has()取到
    -+0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
    -undefined 与 undefined 是恒等的,所以不重复;
    -Set 结构的键名就是键值

    基本用法:

    Set()是一个构造函数,需要用new来实例化,Set()构造函数可以接收一个具有 iterable 接口的其他数据结构(如数组,Map和Set结构等数据),其他类型会报错

    // Set()可以接收一个数组参数
    let set1 = new Set([1]);
    
    // Set()可以接收一个Map结构数据参数
    let map = new Map([[1,"one"]])
    let set2 = new Set(map);
    
    // Set()可以接收一个Set结构数据参数
    let set3 = new Set([1]);
    let set4 = new Set(map);
    
    // Set()可以接收一个字符串作为参数,相当于将字符串使用split()方法后再传入Set()
    let set10 = new Set("abc");
    // Set(3) {"a", "b", "c"}
    
    // Set()可以接收一个null作为参数
    let set5 = new Set(null);  // Set(0) {}
    
    // Set()可以接收一个undefined作为参数
    let set6 = new Set(undefined); // // Set(0) {}
    
    // Set()不能接收一个NaN作为参数
    let set7 = new Set(NaN);
    // Uncaught TypeError: number NaN is not iterable
    
    
    // Set()不能接收一个Object作为参数
    let set8 = new Set({});
    // Uncaught TypeError: object is not iterable
    
    // Set()不能接收一个Bollean作为参数
    let set9 = new Set(true);
    // Uncaught TypeError: boolean true is not iterable
    

    注意:null和undefined作为参数时相当于不传参数,即直接new Set()

    Set的属性

    Set.prototype.constructor:构造函数,默认就是Set函数。
    Set.prototype.size:返回Set实例的成员总数。

    let set = new Set([1,2,3,4]);
    set.constructor;    //  ƒ Set() { [native code] }
    set.size;   // 4
    

    Set的方法

    Set.prototype.add(value):添加某个值,返回 Set 结构本身。
    Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
    Set.prototype.clear():清除所有成员,没有返回值。

    let set1 = new Set();
    // set的add方法可以添加任何类型的数据
    // 添加数字
    set1.add(1);
    // 添加字符串
    set1.add("abc");
    // 添加布尔值true
    set1.add(true);
    // 添加布尔值false
    set1.add("abc");
    // 添加null
    set1.add(null);
    // 添加undefined
    set1.add(undefined);
    // 添加NaN
    set1.add(NaN);
    // 添加数组
    set1.add([1,2,3]);
    // 添加对象
    set1.add({name:"张三"})
    // 添加Symbol数据
    set1.add(Symbol("123"));
    // 添加Map数据
    set1.add(new Map([[1,"one"]]));
    // 添加Set数据
    set1.add(new Set([1]));
    // 添加函数
    set1.add(function add(){});
    console.log(set1);
    // 结果为:Set(12) {1, "abc", true, null, undefined, …}
    
    // has()检测是否含有某个Set成员
    set1.has(1);  // true
    set1.has(NaN);  // true  NaN不严格相等,但是在Set中它是一个唯一值,即能直接用NaN检索到
    set1.has(Symbol("123"));  // false
    // Symbol类型得通过变量定义才能被检测到
    
    // delete()删除某个Set成员
    set1.delete(NaN); 
    set1.has(NaN);  // false
    
    set1.has(undefined);  // true
    set1.clear();
    set1.has(undefined);   // false
    

    Set的遍历(迭代)

    for...of

    Set结构原生提供三个遍历器生成函数:
    -Set.prototype.keys():返回键名的遍历器。
    -Set.prototype.values():返回键值的遍历器。
    -Set.prototype.entries():返回所有成员的遍历器。

    let set = new Set(['red', 123, true,null]);
    
    for (let item of set.keys()) {
      console.log(item);
    }
    // red
    // 123
    // true
    // null
    
    for (let item of set.values()) {
      console.log(item);
    }
    // red
    // 123
    // true
    // null
    
    for (let item of set.entries()) {
      console.log(item);
    }
    // ["red", "red"]
    // [123, 123]
    // [true, true]
    // [null, null]
    

    set.entries()方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。

    Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法,因此可以省略values方法,直接用for...of循环遍历 Set。

    let set = new Set(['red', 123, true,null]);
    for (let item of set) {
      console.log(item);
    }
    // red
    // 123
    // true
    // null
    
    forEach()
    let set = new Set(['red', 123, true,null]);
    set.forEach((value, key) => console.log(key + ' : ' + value))
    // red : red
    // 123 : 123
    // true : true
    // null : null
    

    Set的应用场景

    数组去重
    let arr = [1,1,2,3,3,2,4,5,4];
    let set = new Set(arr);
    console.log(arr); // [1,1,2,3,3,2,4,5,4]
    arr = [...set];  
    console.log(arr); // [1, 2, 3, 4, 5]
    
    字符串去重
    let str= "aabbccddab";
    let set = new Set(str);
    console.log(str); //  "aabbccddab"
    str= [...set].join("");  
    console.log(str); 
    // "abcd"
    
    求数组并集
    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    let set= new Set([...a, ...b]);
    let union = [...set];
    console.log(union);   // [1, 2, 3, 4]
    
    求数组交集
    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    let set= new Set([...a].filter(x => b.has(x))); 
    let intersect = [...set];
    console.log(intersect );   // [ 2, 3]
    
    求数组差集
    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    
    // 方法一:先求并集、交集
    // 先求出并集
    let set = new Set([...a, ...b]);
    // 再求出交集
    let set1= new Set([...a].filter(x => b.has(x))); 
    // 再利用并集求差集
    let set2 = new Set([...set].filter(x => !set1.has(x))); 
    let difference = [...set2];
    console.log(difference);   // [1, 4]
    
    // 方法二:分别求差集,再求并集
    let set3=new Set([...[...a].filter(x => !b.has(x)),...[...b].filter(x => !a.has(x))]);
    let difference1 = [...set3]  // [1, 4]
    

    工作中,若交集、并集都需要求,那适合用第一种方法,若只需要差集,那可以用第二种方式。

    相关文章

      网友评论

        本文标题:ES6—Map和Set数据结构

        本文链接:https://www.haomeiwen.com/subject/cmnkvctx.html