我知道的Set 和 Map

212 阅读5分钟

浅谈一下Set和Map

145f6429bc89c50.jpg

前言

在每次使用ES6新特性的时候,我们都已经非常习惯这些语法,但在近期的使用中,我对Set和Map的概念和应用场景等,出现了一些很模糊的概念,因此打算借此机会复习一下set和map。

Set

Set是ES6新增的数据结构,一种叫做集合的数据结构,它类似于数组,但是成员的值都是唯一的(也就是说不能重复),并且是无序的。

set本身是一个构造函数,可以用来生成set数据结构。可以接受一个数组(或者其他具有iterable 接口的其他数据结构)作为参数,用来初始化。

let set = new Set([1, 2, 3])

console.log(set);   // Set(3) { 1, 2, 3 }

set实例的方法和属性

  • 属性:
  1. Set.prototype.size:返回Set实例的成员总数。
  2. Set.prototype.constructor:构造函数,默认指向Set函数
  • 操作方法:
  1. Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  2. Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  4. Set.prototype.clear():清除所有成员,没有返回值。
  • 遍历方法
  1. Set.prototype.keys():返回键名的遍历器
  2. Set.prototype.values():返回键值的遍历器
  3. Set.prototype.entries():返回键值对的遍历器
  4. Set.prototype.forEach():使用回调函数遍历每个成员

由于其特性多用于数组去重

[...new Set([1, 1, 2, 2, 3, 4])]  // [1, 2, 3, 4]

字符串去除重复字符

[...new Set('aabbccdde')].join('')  // 'abcde'

Set数据结构成员唯一性

Set添加成员使用。add()方法,在该方法中会对添加的成员进行校验,判断在实例中是否已经存在该成员,存在就不会添加,反之就添加。其中判断成员的类型也属于不同,判断的标准相当于 ===

但有一点不同在于:

console.log(NaN === NaN);  // false

let set = new Set()
set.add(NaN)
set.add(NaN)
console.log(set.size);  // 1

运算符===会判断NaN不等于自身,而Set会判断NaN等于自身。

Set数据结构的遍历

之前说过,Set类型是无序的,因此遍历的顺序就是插入的顺序

Set数据类型没有键名只有键值,因此实例的key和value是相同的,因此Set.prototype.keys()Set.prototype.values()返回的结果是一样的。

Set结构的实例默认可以遍历,并且values()方法就是它默认调用的遍历方法。

Set.prototype[Symbol.iterator] === Set.prototype.values

也就是说Set实例可以使用 for of遍历

Set实现交集,并集,差集

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}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

Map

在JavaScript中对象本质上是key/value结构,而传统key只能使用字符串,这就带来很大限制,因此ES6 提供了 Map 数据结构。

定义

Map数据结构类似于对象,也是key/value结构,但是map的key的范围不限于字符串,各种类型的值(包括对象)都可以当作键

let map = new Map()
let o = {a: 1}
map.set(o, 'hahah')

console.log(map);  // Map(1) { { a: 1 } => 'hahah' }

Map作为构造函数,也可以接受一个数组作为参数。这个数组的成员表示一个个的键值对。任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。

let arr = [
  ['name', 'birde'],
  ['sex', 'man'],
]
let map = new Map(arr)

console.log(map); // Map(2) { 'name' => 'birde', 'sex' => 'man' }

// 使用set结构做参数
let set = new Set()
set.add(['name', 'birde'])
let map2 = new Map(set)

console.log(map2);  // Map(1) { 'name' => 'birde' }

// 使用map结构做参数
let map3 = new Map(map)
console.log(map3);  // Map(2) { 'name' => 'birde', 'sex' => 'man' }

对同一键名多次赋值,后面的会覆盖前面的

因为map可以使用对象来充当key值,但object是引用属性,因此只有对同一个对象的引用才能视为是同一个键

let a = [2]
map.set([1], 111)
map.set(a, '1231')

console.log(map.get([1]));  // undefined
console.log(map.get(a));  // 1231

如果key值为基础属性,那就只要判断key严格相等就可以了,比如+0 和 -0为同一个值,'true' 和 true不是同一个值,和Set判断一样,map也会将NaN判断为同一个key

map实例的方法和属性

  • 属性:
  1. Map.prototype.size:返回Map实例的成员总数。
  2. Map.prototype.constructor:构造函数,默认指向Map函数
  • 操作方法:
  1. Map.prototype.set(key, value): set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

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

  1. Map.prototype.get(key): get方法读取key对应的键值,如果找不到key,返回undefined
  2. Map.prototype.has(key): has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  3. Map.prototype.delete(key): delete方法删除某个键,返回true。如果删除失败,返回false
  4. Map.prototype.clear(): clear方法清除所有成员,没有返回值。
  • 遍历方法:和Set大致相同,可以参考上面。
  1. Set结构的实例默认可以遍历,并且entries方法就是它默认调用的遍历方法。

Map与其他数据类型的转换

  1. Map 转 Array
[...map]  // 解构
  1. Array 转 Map
new Map(arr)  // 数组需要每个成员都有键值对
  1. Map 转 对象 因为对象的key只能是字符串,因此如果Map中所有的key都是字符串格式,那么转换就不会有差异。
let temp = {}
for(let [key, value] of map) {
    temp[key] = value
    }

如果map的key中有非字符串,那么就会被转换成字符串,当作对象的键名。

  1. 对象 转 Map 调用Object.entries()可以获取到对象的key/value组成的数组。

  2. Map 转 JSON

如果map的键名都是字符串类型,则可以先将map转为对象,再使用JSON.stringify()转为JSON格式。

如果map的键名包含引用类型,那就可以将map转为数组,再使用JSON.stringify()转为JSON格式。

  1. JSON 转 Map 使用JSON.parse(),得到对象或者数组,再调用相应方法转为map。

总结

多看书,多动手!

阮一峰老师的ES6教程