ES6系列——Set和Map数据结构

ღ( ´・ᴗ・` )每天告诉自己编程是世界上最简单的事情,是不是很开心ღ


每当需要ES6语法的时候,都要去查阅阮一峰老师写的【ES6入门】,强烈推荐,传送门:http://es6.ruanyifeng.com/。
对于Set的用法仅仅停留在数组去重这个层面上,例如仅仅知道通过Array.from(new Set([1,2,4,5,6,4]) )可以实现数组去重,对Map的了解更是知之甚少。而且并不知道深层原因是什么?并且不明白实现Set和Map这两种数据结构,目的是什么,意义又是什么呢??没有认真研究这一部分的时候,一直很困惑。所以认真去查阅了资料,通过查阅资料了解到:Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构。Set和Map主要的应用场景在于数组去重和数据存储。
接下来,请随我一起徜徉在Set和Map这两种数据结构的知识海洋里吧!!!
一、Set数据结构
1、基本用法
我们在前面刚刚提到Set是一个集合?它又有什么功能呢?它和数组有什么不同之处呢?下面我们带着疑问一起来看一下:

  • 集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组

  • ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值

  • Set 本身是一个构造函数,用来生成 Set 数据结构

  • 这里说的Set其实就是我们所要讲到的集合,先来看下基础用法:
    let array = [1, 2, 5, 4, 7];console.log(array);
    arr的输出结果:
    let set= new Set([1, 2, 5, 4, 7]);console.log(set);
    set的输出结果:
    从上面两者输出的内容就可以看出他们的不同:
    因为他没有我们数组的长度(length),只有个数(size),把个体放进一个花括号内,因此称呼Set为一个集合。
    set集合里面的元素不会重复,也就是唯一的,默认的值是value值,没有key。
    2、 Set 实例的属性和方法
    2.1 Set 结构的实例有以下属性:
    Set.prototype.constructor:构造函数,默认就是Set函数。
    Set.prototype.size:返回Set实例的成员总数。
    2.2 Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
    2.2.1操作方法
    add(value):添加某个值,返回 Set 结构本身。
    delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    has(value):返回一个布尔值,表示该值是否为Set的成员。
    clear():清除所有成员,没有返回值。
    属性和方法的例子如下:
    let s = new Set(); //Set本身是一个构造函数,用来生成 Set 数据结构
    s.add(1).add(2).add(2);// 注意2被加入了两次结果,但是 Set 结构不会添加重复的值
    s.size // 2
    s.has(1) // true
    s.has(2) // true
    s.has(3) // false
    s.delete(2);
    s.has(2) // false

  • Set结构判断是否包括一个键值的方法:
    // Set的写法
    const properties = new Set();
    properties.add(‘width’);
    properties.add(‘height’);
    if (properties.has(someName)) {
    // do something
    }
    与Object结构的判断方法是不同的,Object结构的判断方法如下:
    // 对象的写法
    const properties = {
    ‘width’: 1,
    ‘height’: 1
    };
    if (properties[someName]) {
    // do something
    }

  • Array.from方法可以将 Set 结构转为数组
    前文提到过,Set数据结构不是数组,而是一个集合。下面这个知识点就是告诉大家,可以通过Array.from方法将Set 结构转为数组(关于Array.from,参考之前写的一篇“数组的扩展”章节,本节不做赘述)。
    实例如下:

    2.2.2 遍历操作
    Set 结构的实例有四个遍历方法,可以用于遍历成员。
    keys( ):返回键名的遍历器
    values( ):返回键值的遍历器
    entries( ):返回键值对的遍历器
    forEach( ):使用回调函数遍历每个成员

  • keys( )、values( )、entries( )
    keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为是一致的。
    举例如下:
    let set = new Set([‘red’, ‘green’, ‘blue’]);
    for (let item of set.keys()) {
    console.log(item);
    }
    // red
    // green
    // blue

      for (let item of set.values()) {
        console.log(item);
      }
      // red
      // green
      // blue
    
      for (let item of set.entries()) {
        console.log(item);
      }
      // ["red", "red"]
      // ["green", "green"]
      // ["blue", "blue"]
    

而通过上面的例子可以看出,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。

  • 可以直接用for…of循环遍历 Set
    因为Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。因此,可以省略values方法,直接用for…of循环遍历 Set。
    举例如下:
    let set = new Set([‘red’, ‘green’, ‘blue’]);

      for (let x of set) {
        console.log(x);
      }
      // red
      // green
      // blue
    
  • forEach( )
    Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。

      let set = new Set([1, 4, 9]);
      set.forEach((value, key) => console.log(key + ' : ' + value))
      // 1 : 1
      // 4 : 4
      // 9 : 9
    

该函数的参数与数组的forEach一致,依次为键值、键名、集合本身。forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
3、应用

  • Array 与 Set 相互转换
    var myArray = [“value1”, “value2”, “value3”];
    // 用Set构造器将Array转换为Set
    var mySet = new Set(myArray);
    mySet.has(“value1”); // returns true

      // 用...(扩展运算符)操作符将Set转换为Array
      console.log([...mySet]); // 与myArray完全一致
    
  • 数组去重
    let arr = [3, 5, 2, 2, 5, 5];
    // 用数组静态方法 Array.from
    let unique =Array.from(new Set(arr));// [3, 5, 2]
    // 或用扩展运算符(…)
    let unique = […new Set(arr)];// [3, 5, 2]

  • 字符串转成 Set
    var text = “happy”;
    var stringSet = new Set(text);
    console.log(sringSet); // Set(5) {“h”, “a”, “p”, “p”, “y”}
    stringSet.size; //5

  • 实现并集、交集、差集
    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    // 并集
    let union = new Set([…a, …b]); // Set {1, 2, 3, 4}
    // 交集
    let intersect = new Set([…a].filter (x => b.has(x))); // Set { 2, 3}
    // 差集
    let difference = new Set([…a].filter (x => !b.has(x))); // Set { 1 }
    二、Map数据结构
    1.基本用法
    JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
    为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
    一个Map对象以插入顺序迭代其元素 — 一个 for…of 循环为每次迭代返回一个[key,value]数组。
    使用方式:new Map([iterable])
    参数 iterable:可以是一个数组或者其它的 iterable 对象,其元素或为键值对,或为两个元素的数组。
    const m = new Map();
    const o = {p: ‘Hello World’};

      m.set(o, 'content')
      m.get(o) // "content"
    
      m.has(o) // true
      m.delete(o) // true
      m.has(o) // false
    

上面的例子展示了如何向 Map 添加成员。作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
[‘name’, ‘张三’],
[‘title’, ‘Author’]
]);

	map.size // 2
	map.has('name') // true
	map.get('name') // "张三"
	map.has('title') // true
	map.get('title') // "Author"

如果对同一个键多次赋值,后面的值将覆盖前面的值。
const map = new Map();

	map
	.set(1, 'aaa')
	.set(1, 'bbb');

	map.get(1) // "bbb"

注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map();

	map.set(['a'], 555);
	map.get(['a']) // undefined

上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
**由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。**这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
2、Map 实例的属性和方法
2.1 属性

  • size 属性
    size属性返回 Map 结构的成员总数。

      const map = new Map();
      map.set('foo', true);
      map.set('bar', false);
    
      map.size // 2
    
  • set(key, value)
    set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

      const m = new Map();
    
      m.set('edition', 6)        // 键是字符串
      m.set(262, 'standard')     // 键是数值
      m.set(undefined, 'nah')    // 键是 undefined
    

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

	let map = new Map()
	  .set(1, 'a')
	  .set(2, 'b')
	  .set(3, 'c');
  • get(key)
    get方法读取key对应的键值,如果找不到key,返回 undefined。

      const m = new Map();
    
      const hello = function() {console.log('hello');};
      m.set(hello, 'Hello ES6!') // 键是函数
    
      m.get(hello)  // Hello ES6!
    
  • has(key)
    has方法返回一个布尔值,表示某个键是否在当前 Map 对象 之中。
    const m = new Map();

      m.set('edition', 6);
      m.set(262, 'standard');
      m.set(undefined, 'nah');
    
      m.has('edition')     // true
      m.has('years')       // false
      m.has(262)           // true
      m.has(undefined)     // true
    
  • delete(key)
    delete方法删除某个键,返回true。如果删除失败,返回false。
    const m = new Map();
    m.set(undefined, ‘nah’);
    m.has(undefined) // true

      m.delete(undefined)
      m.has(undefined)       // false
    
  • clear()
    clear方法清除所有成员,没有返回值。
    let map = new Map();
    map.set(‘foo’, true);
    map.set(‘bar’, false);

      map.size // 2
      map.clear()
      map.size // 0
    

2.2 遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。
举例如下:

 	const map = new Map([
	  ['F', 'no'],
	  ['T',  'yes'],
	]);
	
	
	for (let key of map.keys()) {
	  console.log(key);
	}
	// "F"
	// "T"

	for (let value of map.values()) {
	  console.log(value);
	}
	// "no"
	// "yes"
	
	for (let [key, value] of map.entries()) {
	  console.log(key, value);
	}
	// "F" "no"
	// "T" "yes"

Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)。举例如下:

	const map = new Map([
	  [1, 'one'],
	  [2, 'two'],
	  [3, 'three'],
	]);

	[...map.keys()]
	// [1, 2, 3]

结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤。跟Set数据结构利用该方法类似。

	const map0 = new Map()
	.set(1, 'a')
	.set(2, 'b')
	.set(3, 'c');

	const map1 = new Map(
	[...map0].filter(([k, v]) => k < 3)
	);
	// 产生 Map 结构 {1 => 'a', 2 => 'b'}

	const map2 = new Map(
	[...map0].map(([k, v]) => [k * 2, '_' + v])
	);
	// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

此外,Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。
3.应用

  • Map 与 数组之间的相互转换
    // Map 转数组
    var myMap = new Map();
    myMap.set(“bar”, “foo”);
    myMap.set(1, “bar”);
    […myMap]; // [ [“bar”, “foo”], [1, “bar”] ]

      // 数组转Map
      const arr = new Map( [ ["bar", "foo"], [1, "bar"] ]);
      console.log(arr);  // Map {"bar" => "foo", 1 => "bar"}
    
  • Map 与对象相互转换
    // Map 转对象
    function strMapToObj(strMap) {
    let obj = Object.create(null);
    for (let [k, v] of strMap) {
    obj[k] = v;
    }
    return obj;
    }
    const myMap = new Map();
    myMap.set(“bar”, “foo”)
    .set(1, “ooo”);

      strMapToObj(myMap ); // Object {1: "ooo", bar: "foo"}
    
      // 对象转 Map
      function objToStrMap(obj) {
        let strMap = new Map();
        for (let k of Object.keys(obj)) {
      	strMap.set(k, obj[k]);
        }
        return strMap;
      }
      objToStrMap({1: "ooo", bar: "foo"}); // Map {"1" => "ooo", "bar" => "foo"}
    
  • Map 与 JSON 相互转换
    // Map 转 JSON
    // Map 的键名为字符串
    function strMapToJson(jsonStr) {
    return JSON.stringify(strMapToObj(jsonStr));
    }
    const myMap = new Map();
    myMap.set(“bar”, “foo”)
    .set(1, “ooo”);
    strMapToJson(myMap); // “{“1”:“ooo”,“bar”:“foo”}”

      // Map 的键名为非字符串
      function mapToArrayJson(map) {
        return JSON.stringify([...map]);
      }
      mapToArrayJson(myMap); // "[["bar","foo"],[1,"ooo"]]"
    
      // Json 转 Map
      // 正常情况下所有键名都为字符串
      function jsonToStrMap(jsonStr) {
        return objToStrMap(JSON.parse(jsonStr));
      }
      jsonToStrMap("{"1":"ooo","bar":"foo"}"); // Map {"1" => "ooo", "bar" => "foo"}
    
      // 整个JSON 是数组
      function jsonToMap(jsronStr) {
        return new Map(JSON.parse(jsronStr)); 
      }
      jsonToMap([["bar","foo"],[1,"ooo"]]); // Map {"1" => "ooo", "bar" => "foo"}
    

Map数据结构是具备json的所有功能,还多出了命名是任意类型,所以Map就是强化版的json。
以上就是这一章节的知识点啦!
哇,又到了跟大家说再见的时候了,其实Set和Map数据结构在属性和方法上有很多类似点,希望大家能多多消化这一块的知识,我们下次见呀。


ღ( ´・ᴗ・` )每天告诉自己编程是世界上最简单的事情,是不是很开心ღ

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值