Set、Map、WeakSet 和 WeakMap 的区别,你真的很清楚吗?

253 阅读7分钟

Set(集合)

  1. 是一种叫做集合的数据结构(ES6新增的)
  2. 类似于数组,可以说是伪数组了(因此可以用Array.from转成数组,或者ES6的...扩展运算符)
    const items = new Set([1, 2, 3, 1])
    const array = Array.from(items)
    console.log(array)	// [1, 2, 3]
    // 或者
    const arr = [...items]
    console.log(arr)	// [1, 2, 3]
    
  3. 本身是一个构造函数,用来生成Set数据结构
    new Set([iterable])
    
  4. 成员唯一、无序且不重复
    const s = new Set([1,2,3,5,4,3,2,1]) //1 2 3 5 4
    
  5. [value, value],键值与键名是一致的(或者说 只有键值,没有键名)
  6. 允许储存任何类型的唯一值,无论是原始值或者是对象引用
    new Set({a:"123",b:"1234"})//是不行的 {a:"123",b:"1234"}这个不是可迭代的
    const s = new Set();
    s.add({a:"123",b:"1234"})//这样可行的
    
  7. 可以遍历 用for of
    const s = new Set([1,2,3,5,4,3,2,1]) //1 2 3 5 4
    for (let i of s) {
        console.log(i)	// 1 2 3 5 4
    }
    
  8. 向Set add值的时候,不会发生类型转换
    let s = new Set()
    s.add(1)
    s.add('1')
    console.log([...s])	// [1, "1"]
    
  9. Set 实例属性有:
    1. constructor: 构造函数
    2. size:元素数量
      let set = new Set([1,2,3,5,4,3,2,1])
      console.log(set.size)	// 3
      console.log(set.length)	// undefined
      
    • 注意:没有length属性
  10. 操作方法有:add、delete、has、clear
    1. add(value):新增,相当于 array里的push
    2. delete(value):存在即删除集合中value
    3. has(value):判断集合中是否存在 value
    4. clear():清空集合
    let set = new Set()
    set.add(1).add(2).add(3)
    set.has(1)	// true
    set.has(4)	// false
    set.delete(1)	
    set.has(1)	// false
    
  11. 遍历方法有:keys values entries forEach(遍历顺序为插入顺序)
    1. keys():返回一个包含集合中所有键的迭代器
    2. values():返回一个包含集合中所有值得迭代器(其实keys()跟values()是一样的因为键值与键名是一致的) 如图:
    3. entries():返回一个包含Set对象中所有元素得键值对迭代器
        其实就是键跟值一样的结构如下截图
    
    4.forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值
  12. Set 可默认遍历,默认迭代器生成函数是 values() 方法
    Set.prototype[Symbol.iterator] === Set.prototype.values	// true
    
  13. Set可以使用 map、filter 方法
  14. Set 可以很方便的实现交集(Intersect)、并集(Union)
    let set1 = new Set([1, 2, 3, 5])
    let set2 = new Set([4, 3, 2, 1])
    let intersect = new Set([...set1].filter(value => set2.has(value)))
    let union = new Set([...set1, ...set2])
    console.log(intersect)	// Set(3) {1, 2, 3}
    console.log(union)		// Set(5) {1, 2, 3, 5, 4}
    

WeakSet

  1. 成员都是对象
  2. 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
  3. 不能遍历
  4. 与 Set 的区别如下:
    • WeakSet 只能储存对象引用不能存放值,而 Set 值与对象都可以
    • WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于WeakSet中),所以,WeakSet对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了),WeakSet 对象是无法被遍历的(ES6 规定 WeakSet 不可遍历),也没有办法拿到它包含的所有元素
  5. WeakSet 实例属性有:
    • constructor:构造函数,任何一个具有 Iterable 接口的对象,都可以作参数
  6. 操作方法有:add、delete、has、clear方法没有
    1. add(value):在WeakSet 对象中添加一个元素value
    2. has(value):判断 WeakSet 对象中是否包含value
    3. delete(value):删除元素 value
    4. clear():清空所有元素,注意该方法已废弃
    var wst = new WeakSet()
    var obj = {}
    var abc = {}
    wst.add(window)
    wst.add(obj)
    wst.has(window)	// true
    wst.has(abc)	// false
    wst.delete(window)	// true
    wst.has(window)	// false
    

Map(字典)

  1. 是一种叫做字典的数据结构
  2. 本质上是键值对的集合,类似集合
  3. 可以遍历,方法很多可以跟各种数据格式转换
  4. Map 实例属性有:
    1. constructor:构造函数
    2. size:返回字典中所包含的元素个数
    const map = new Map([
      ['name', 'long'],
      ['age', 12]
    ]);
    map.size // 2
    map.length /// undefined
    
    • 注意:没有length属性
  5. Map的操作方法有:set,get,has,delete,clear
    1. set(key, value):向字典中添加新元素
    2. get(key):通过键查找特定的数值并返回
    3. has(key):判断字典中是否存在键key
    4. delete(key):通过键 key 从字典中移除对应的数据
    5. clear():将这个字典中的所有元素删除
    const m = new Map()
    const o = {name: 'long'}
    m.set(o, 'liangliang')
    m.get(o)	// liangliang
    m.has(o)	// true
    m.delete(o)	// true
    m.has(o)	// false
    
  6. 遍历方法有:Keys,values,entries,forEach
    1. Keys():将字典中包含的所有键名以迭代器形式返回
    2. values():将字典中包含的所有数值以迭代器形式返回
    3. entries():返回所有成员的迭代器
    4. forEach(callbackFn,thisArg):遍历字典的所有成员,对集合成员执行callbackFn操作,如果提供了thisArg参数,回调中的this会是这个参数,没有返回值
    const map = new Map([
            ['name', 'long'],
            ['age', 16]
        ]);
    console.log(map.entries())//MapIterator {"name" => "long", "age" => 16}
    console.log(map.keys())//MapIterator {"name", "age"}
    console.log(qmap.values())//MapIterator {"long", 16}
    
    const reporter = {
      report: function(key, value) {
        console.log("Key: %s, Value: %s", key, value);
      }
    };
    let map = new Map([
        ['name', 'long'],
        ['age', 16]
    ])
    //注意:forEach 方法的回调函数的 this,就指向 reporter
    map.forEach(function(value, key, map) {
      this.report(key, value);
    }, reporter);
    // Key: name, Value: long
    // Key: age, Value: 16
    
  7. Map 默认遍历器接口(Symbol.iterator属性),是entries方法
    const map = new Map()
    map[Symbol.iterator] === map.entries // true
    Map.prototype[Symbol.iterator]  === Map.prototype.entries
    
  8. 集合 与 字典 的区别:
    • 共同点:集合,字典 都可以储存不重复的值
    • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存
  9. 注意事项
    • 只有对同一个对象的引用,Map结构才将其视为同一个键,这一点很容易出错
      const map = new Map();
      map.set(['a'], 555);
      map.get(['a']) // undefined
      var scs = ['a'];
      map.set(scs , 666);
      map.get(scs)//666
      //说明:set和get方法,表面是针对同一个键,但实际上这是两个值,
      //内存地址是不一样的,因此get方法无法读取该键,返回undefined,
      //第二个例子就没问题
      
    • 如果 Map 的键是一个简单类型的值(数字,字符串,布尔值),则只要两个值严格相等,Map将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键.另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但Map将其视为同一个键.
  10. Map与其他数据结构的相互转换如下:
    1. Map 转 Array
      const map = new Map([[1, 1], [2, 2], [3, 3]])
      console.log([...map])	// [[1, 1], [2, 2], [3, 3]]
      
    2. Array 转 Map
      const map = new Map([[1, 1], [2, 2], [3, 3]])
      console.log(map)	// Map {1 => 1, 2 => 2, 3 => 3}
      
    3. Map 转 Object
      • 因为Object的键名都为字符串,而Map的键名为对象,所以转换的时候会把非字符串键名转换为字符串键名.
      function mapToObj(map) {
          let obj = Object.create(null)
          for (let [key, value] of map) {
              obj[key] = value
          }
          return obj
      }
      const map = new Map().set('name', 'long').set('age', 16)
      mapToObj(map) // {name: "long", age: "16"}
      
    4. Object 转 Map
      function objToMap(obj) {
          let map = new Map()
          for (let key of Object.keys(obj)) {
              map.set(key, obj[key])
          }
          return map
      }
      objToMap({'name': 'long', 'age': 16})
      // Map {"name" => "long", "age" => 16}
      
    5. Map 转 JSON
      function mapToJson(map) {
          return JSON.stringify([...map])
      }
      let map = new Map().set('name', 'LONG').set('age', 16)
      mapToJson(map)	// [["name","LONG"],["age",16]]
      
    6. JSON 转 Map
      function objToMap(obj) {
          let map = new Map()
          for (let key of Object.keys(obj)) {
              map.set(key, obj[key])
          }
          return map
      }
      function jsonToStrMap(jsonStr) {
        return objToMap(JSON.parse(jsonStr));
      }
      jsonToStrMap('{"name": "long", "age": 16}') 
      // Map {"name" => "long", "age" => 16}
      

WeakMap

  1. 只接受对象作为键名(null除外),不接受其他类型的值作为键名.
  2. 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的.
  3. 不能遍历,方法有get、set、has、delete.
  4. 注意,WeakMap弱引用的只是键名,而不是键值.键值依然是正常引用.
  5. WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的.
  6. 属性:
    • constructor:构造函数
  7. 方法:
    1. has(key):判断是否有 key 关联对象
    2. get(key):返回key关联对象(没有则则返回 undefined)
    3. set(key):设置一组key关联对象
    4. delete(key):移除 key 的关联对象
    let myElement = document.getElementById('logo');
    let myWeakmap = new WeakMap();
    myWeakmap.set(myElement, {timesClicked: 0});
    myElement.addEventListener('click', function() {
      let logoData = myWeakmap.get(myElement);
      logoData.timesClicked++;
    }, false);
    

总结

  1. 这些概念都是新增的,一下子记住还是挺难的,大家最好的记忆就是去运用,在平时的工作中辅之以用,就能运用得手
  2. 最后,喜欢的可以点个赞,谢谢