Map&Set

# 定义 (opens new window)

  • 字典
    • 一些元素的集合,每个元素都有一个称作key的域,不同元素的key各不相同
  • 集合
    • 由一堆无序的、相关联的,且不重复的内存结构组成的组合
  • 区别
    • 共同点: 集合、字典都可以存储不重复的值
    • 不同点: 集合以[值、值]形式存储,字典以['键', '值']形式存储

# Map

  • 是一种字典数据结构
  • 是键值对的有序列表,而键和值都可以是任意类型
  • 本身是一个构造函数,用于生成Map数据结构
  • 增删改查size/set()/get()/has()/delete()/clear()
    • set()

      • 设置键名key对应的键值value,返回整个Map结构

      • 如果key已经有值,则value会被更新,否则新生成键值对,并返回整个Map

      • 可链式调用

        const map = new Map()
        // 键和值都可以是**任意类型**
        map.set('str', 6)
        map.set(666, 'Hello')
        map.set(undefined, {name: 1111})
        // 链式调用
        map.set(1, 'a').set(2, 'b').set(3, 'c')
        
    • get()读取key对应的键值,找不到则返回undefined

      const map = new Map()
      const fn = function() {console.log('hello')}
      map.set(fn, 'HELLO ES6') // 键可以是函数
      map.get(fn) // 'HELLO ES6'
      
    • has()返回一个布尔值,表示某个键是否在当前Map中

      const map = new Map()
      map.set('str', 6)
      map.set(666, 'Hello')
      map.set(undefined, {name: 1111})
      
      map.has('str') // true
      map.has(666) // true
      map.has(undefined) // true
      map.has('fuck') // false
      
    • delete()删除某个键。成功返回true,反之亦然。

      const map = new Map()
      map.set(undefined, 'nono')
      map.has(undefined) // true
      
      map.delete(undefined) // true
      map.has(undefined) // false
      
    • clear()清空所有成员,没有返回值

      const map = new Map()
      map.set('foo', true)
      map.set('bar', 111)
      
      map.size // 2
      map.clear()
      map.size // 0
      
    • size属性返回Map结构的成员总数, 类似array.length

      const map = new Map()
      map.set('foo', true)
      map.set('bar', false)
      map.size // 2
      
    • 遍历(遍历顺序为插入顺序)

      • keys(): 返回键名遍历器
      • values(): 返回键值的遍历器
      • entries(): 返回键值对的遍历器
      • forEach(): 只用回调函数遍历每个成员
      const map = new Map([['f', 'oo'], ['b', 'ar']])
      for (let key of map.keys()) {
          console.log(key)
      }
      // 'f'
      // 'b'
      for (let val of map.values()) {
          console.log(val)
      }
      // 'oo'
      // 'ar'
      // 以下三个方法相等
      for (let item of map.entries()) {
          console.log(item[0], item[1])
      }
      for (let [key, val] of map.entries()) {
          console.log(key, val)
      }
      for (let [key, val] of map) {
          console.log(key, val)
      }
      // 'f', 'oo'
      // 'b', 'ar'
      map.forEach((val, key, map) => {
        console.log('key: ', key, 'val: ', val)  
      })
      // key:  f val:  oo
      // key:  b val:  ar
      

# Set

  • 是一种集合数据结构
  • 类似数组,但成员的值都是唯一的,没有重复,一般称为集合
  • 本身是一个构造函数,用于生成Set数据结构
  • 增删改查add()/delete()/has()/clear()
    • add()添加某个值,返回Set,只处理未添加的值

      const s = new Set()
      s.add(1).add(2).add(2) // 2只会被添加一次
      
    • delete()删除某个值,返回布尔值表示是否删除成功

      s.delete(1)
      
    • has()返回一个布尔值,表示该值是否为Set的成员

      s.has(1)
      
    • clear()清空所有成员,没有返回值

      s.clear()
      
    • 遍历(遍历顺序为插入顺序)

      • keys(): 返回键名遍历器
      • values(): 返回键值的遍历器
      • entries(): 返回键值对的遍历器
      • forEach(): 只用回调函数遍历每个成员
      // keys values entries返回的欧式遍历器对象
      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']
      
      //forEach用于处理每个元素,没有返回值。键名键值都相等。第二个参数用于绑定处理函数的this
      set.forEach((val, key) => console.log(key + ':' + val))
      // red:red
      // green:green
      // blue:blue
      

# 应用

  • 结合扩展运算符去重
// 字符串
let str = '35225'
let unique = [...new Set(str)].join('')
// '352'
// 数组
let arr = ['3', '5', '5']
let unique = [...new Set(arr)] 
// ['3', '5']
  • 实现并集、交集和差集
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
// 并集
let union = new Set([...a, ...b]) // Set(4) {1, 2, 3, 4}
//交集
let intersect = new Set([...a].filter(x => b.has(x))) // Set(2) {2, 3}
// 差集
let diff = new Set([...a].filter(x => !b.has(x))) // Set(1) {1}

# WeakMap&WeakSet

  • WeakMap

    • 只接受对象作为键名(null除外)
    • Map区别
      • 没有遍历操作的API
      • 没有clear()
    const wm = new WeakMap()
    wm.set('foo', 1) // Uncaught TypeError: Invalid value used as weak map key
    const key = {foo: 1}
    wm.set(key, 1)
    wm.get(key) // 1
    
    // 数组也可
    const a1 = [1, 2, 3]
    const a2 = [4, 5, 6]
    wm.set(a1, a2) 
    wm.get(a1) // [4, 5, 6]
    
    const wm2 = new WeakMap([[a1, 'foo'], [a2, 'bar']])
    wm.get(a1) // 'foo'
    
    • 键名所指的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
    const wm = new WeakMap()
    let key = {'foo': 1}
    wm.set(key, 1) // WeakMap {{…} => 1}
    wm.get(key) // 1
    key = null
    wm.get(key) // undefined
    // 在网页的 DOM 元素上添加数据,就可以使用WeakMap结构,当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除
    const wm = new WeakMap();
    const element = document.getElementById('example');
    wm.set(element, 'some information');
    wm.get(element) // "some information"
    
    • WeakMap弱引用的只是键名,不是键值。键值依然正常引用,不会影响键值
    const wm = new WeakMap()
    let key = {}
    let obj = {'foo': 1}
    wm.set(key, obj)
    obj = null
    wm.get(key) // {foo: 1}
    
  • WeakSet

    • 成员只能是引用类型
    • 接受一个具有Iterable接口的对象作为参数
    • Set区别
      • 没有遍历操作的API
      • 没有size
    const ws = new WeakSet([1, 2]) // Uncaught TypeError: Invalid value used in weak set
    
    let obj1 = {foo: 1}
    let obj2 = {bar: 2}
    const ws = new WeakSet([obj1, obj2])
    /*
        WeakSet {{…}, {…}}
        [[Entries]]
        0:
        value: {foo: 1}
        1:
        value: {bar: 2}
        [[Prototype]]: WeakSet
    */
    

# 扩展知识点

  • MapObject的区别
    • Map是纯净的,只含有显示插入的键。Object会有原型上的属性和方法(ES5后可通过Object.create(null)创建一个纯净对象)
    • Mapkey可以是Js支持的所有数据类型。Objectkey必须是简单数据类型(整数、字符串、Symbol)
    • Map有序,迭代时会按照插入顺序返回键值。Object无序
    • Map长度可以通过size获取。Object需要通过Object.key(obj).length
    • Map可迭代。Object得通过获取键来迭代
    • Map通过get()访问。Object通过./[]访问
    • Map通过has()判断元素是否存在。Object通过obj.key === undefined || 'key' in obj || Object.prototype.hasOwnProperty()
    • Map通过set()新增数据。Object通过obj['key'] = val || obj.key = val
    • Map通过delete删除数据。Object通过非原生方法delete obj.id || obj.id = undefined, 第二种方式只是把值设为undefined,属性仍在
    • Map继承自Object
  • 如何选择?
    • 使用Object的情况

      • 存储简单数据类型,且key都为简单数据类型时用Object, 创建方式更高效
      • 需要在单独的逻辑钟访问属性或元素时候用Object
      var obj = {
          id: 1, 
          name: "It's Me!", 
          print: function(){ 
              return `Object Id: ${this.id}, with Name: ${this.name}`;
          }
      }
      console.log(obj.print())
      
      • JSON支持Object,但不支持Map
    • 使用Map的情况

      • Map在存储大量数据的时候性能更好,特别在执行代码时不知道key的情况下
      • Map会保持元素插入顺序, Object不行
      • 删除操作频繁的情况下使用MapMap是纯粹的hash,Object存在一起内部逻辑,使用delete会有性能问题。