ES6:Map

115 阅读5分钟

一、Map是什么

Map的本质是一个对象:

      const map = new Map()

      console.log(Object.prototype.toString.call(map)) // [object Map]

注意点:

  1. Map可以保存一个键值对,和object不同的是,object的键只能是字符串(数字也会转为字符串类型)和symbol,而Map的键可以是任意值,并且Map中的键一定是唯一的:
      const map = new Map([
        ['a', 1],
        ['a', 2], // 第二个'a'会覆盖第一个'a',Map中的键只能出现一次
        [{}, 3] // Map可以用任何值当做键
      ])

      console.log(map) // Map(2) {'a' => 2, {…} => 3}
  1. Map采用零值相等算法进行比较,Map曾经使用同值相等(+0和-0是不同的两个值,符合Object.is)

这意味着,除了NaN(多个NaN作为键会覆盖前面的键),其他所有的键采用===判断是否相等,Set去重也是这个逻辑

      const map = new Map([[NaN, 1]])

      map.set(NaN, 2)
      map.set(+0, 'a')
      map.set(0, 'b')

      console.log(map) // Map(2) {NaN => 2, 0 => 'b'}

二、如何创建Map

通过new调用Map构造函数,传入一个二维数组作为Map的初始值,通过set方法可以追加值。

      const map = new Map([['a', 1]])

      map.set(true, true)
      map.set(1, 1)

      console.log(map) // Map(3) {'a' => 1, true => true, 1 => 1}

三、Map实例的属性和方法

1、set:追加属性

      const map = new Map([[1, 1]])
      map.set('2', '2')

      console.log(map) // Map(2) {1 => 1, '2' => '2'}

不要使用这种方式去追加map对象的属性:

      const map = new Map()
      map['a'] = 'a'
      map['b'] = 'b'

      console.log(map) // Map(0) {a: 'a', b: 'b', size: 0}
      console.log(map.get('a')) // undefined

虽然不会报错,但是这样写没有实质意义,正确的方式是使用set或者使用Map构造函数时初始化一个二维数组

2、get:获取键对应的值

      const map = new Map([[1, 1]])
      console.log(map.get(1)) // 1

3、has:判断是否有某个键

      const map = new Map([[1, 1]])
      console.log(map.has(1)) // true
      console.log(map.has('1')) // false

4、delete:删除某个键值对

      const map = new Map([
        [1, 1],
        ['2', 2]
      ])
      map.delete(1)
      console.log(map) // Map(1) {'2' => 2}

5、clear:清空所有的键值对

      const map = new Map([
        [1, 1],
        ['2', 2]
      ])
      map.clear()
      console.log(map) // Map(0) {size: 0}

6、size:获取map中有几个键值对

      const map = new Map([
        [1, 1],
        ['2', 2]
      ])
      console.log(map.size) // 2

7、keys/values/entries

      const map = new Map([
        [1, 1],
        ['2', 2]
      ])
      const keys = map.keys()
      const values = map.values()
      const entries = map.entries()
      console.log(keys) // MapIterator {1, '2'}
      console.log(values) // MapIterator {1, 2}
      console.log(entries) // MapIterator {1 => 1, '2' => 2}

8、forEach:以插入的顺序遍历map

      const map = new Map([
        ['a', 'a'],
        [1, 1],
        ['2', 2]
      ])
      map.forEach((value, key) => {
        console.log(value, key)
      })

四、Map和Object的比较

Map和Object相同的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值,因此在Map之前一直拿对象当做Map使用。但现在有了Map,以下情况使用Map会更好:

1、当做字典使用时,意外的键

Map默认不包含任何键,只包含显式插入的键

      const arr = [
        [0, '西游记'],
        [1, '水浒传'],
        [2, '红楼梦'],
        [3, '三国演义']
      ]

      const map = new Map(arr)

      console.log(map) // Map(4) {0 => '西游记', 1 => '水浒传', 2 => '红楼梦', 3 => '三国演义'}
      console.log(map.get(0)) // 西游记

以上字典的写法通过对象也可以实现:

      const obj = {
        0: '西游记',
        1: '水浒传',
        2: '红楼梦',
        3: '三国演义'
      }

      console.log(obj[0])

但是,对象是有原型的,原型上的键名可能与你自己在对象上设置的键名重复。这样便存在了隐患,尽管这样的隐患很小,但是Map是不存在这样的隐患的

当然了,你可以这么做:

      const obj = {
        0: '西游记',
        1: '水浒传',
        2: '红楼梦',
        3: '三国演义'
      }

      const noPrototypeObj = Object.create(null) // 没有原型的对象
      Object.assign(noPrototypeObj, obj)
      console.log(noPrototypeObj)

但是很少有人这样做

2、Map的键和对象的键

对象的键只能是字符串/数字,ES6提供了Symbol,Symbol也可以当做对象的键:

      const name = Symbol()
      const age = Symbol()

      const obj = {
        [name]: 'zhang',
        grade: '高一',
        1: 1
      }
      obj[age] = 18
      console.log(obj) // {1: 1, grade: '高一', Symbol(): 'zhang', Symbol(): 18}

Map对象的键可以是任意值,可以是对象/函数等(并且Map的打印顺序完全按照插入顺序来):

      const arr = [
        [{}, 1],
        [[], 2],
        [function () {}, 3]
      ]

      const map = new Map(arr)
      console.log(map) // Map(3) {{…} => 1, Array(0) => 2, ƒ => 3}

3、键的顺序

Map对象中键是有序的,当遍历Map时,按照插入的顺序返回键值对:

      const arr = [
        [{}, 1],
        [[], 2],
        [function () {}, 3]
      ]

      const map = new Map(arr)
      console.log(map) // Map(3) {{…} => 1, Array(0) => 2, ƒ => 3}

      for (const item of map) {
        console.log(item)
      }
      // [{…}, 1]
      // [Array(0), 2]
      // [ƒ, 3]

对象的遍历首先想到使用for...in,它在遍历对象时的顺序:首先找到对象中的非负整数属性,将这一部分的属性按照升序遍历,再找到其他的属性,按照创建时的顺序遍历出来。参考:for in遍历的顺序

      const name = Symbol()
      const obj = {
        [name]: 'zhang',
        grade: '高一',
        1: 1,
        '-1': -1
      }
      console.log(obj) // {1: 1, grade: '高一', -1: -1, Symbol(): 'zhang'}

      // for...in仅包含以字符串为键的属性
      for (const key in obj) {
        console.log(key, obj[key])
      }
      // 1 1
      // grade 高一
      // -1 -1

以上代码可以看出,for...in无法遍历Symbol键,它只能遍历以字符串为键的属性

同样的,Object.keys()也无法拿到Symbol键:

      const name = Symbol()
      const obj = {
        [name]: 'zhang',
        grade: '高一',
        1: 1,
        '-1': -1
      }
      console.log(obj) // {1: 1, grade: '高一', -1: -1, Symbol(): 'zhang'}

      obj.__proto__.a = 'a'
      Object.prototype.b = 'b'
      const keys = Object.keys(obj)
      console.log(keys) // ['1', 'grade', '-1']

与for...in相比,Object.keys()不会获取到原型上的属性

4、获取键值对的个数

Map可以通过size属性轻松获取键值对的个数,而对象需要手动计算

5、迭代

Map内部实现了迭代器,所以可以使用for...of进行迭代,并且Map对象自带forEach方法也可以进行迭代;

对象的迭代需要使用for...in/Object.keys()/Object.getOwnPropertyNames()/Object.getOwnPropertySymbols()等方式

6、性能

如果频繁地增删键值对,Map的性能表现更优,对象没有对此进行优化

7、序列化

Map不支持直接序列化,对象可以

五、Map和数组的相互转换

1、数组转为Map:使用Map构造函数可以将一个二维键值对数组转为Map对象

      const arr = [
        [1, 1],
        ['2', '2'],
        [true, true]
      ]

      const map = new Map(arr)

2、Map对象转为数组

      const array = [...map]
      const array1 = Array.from(map)

或者只想获取map的键集合:

      const array2 = Array.from(map.keys())

六、遍历Map的方式

1、使用map自带的forEach

      const map = new Map([
        ['a', 'a'],
        [1, 'one'],
        ['2', 'two']
      ])
      map.forEach((value, key) => {
        console.log(key, value)
      })

2、使用for...of,map内部实现了迭代器

      const map = new Map([
        ['a', 'a'],
        [1, 'one'],
        ['2', 'two']
      ])
      for (const [key, value] of map) {
        console.log(key, value)
      }

如果只想遍历键:

      for (const key of map.keys()) {
        console.log(key)
      }

如果只想遍历值:

      for (const value of map.values()) {
        console.log(value)
      }

七、Map的实际用途

1、Map对象是很好的字典表

const userType = [
  [1, 'OA'],
  [2, '代维'],
  [3, '外协']
]
const userTypeMap = new Map(userType)