如何理解Map、WeakMap、Set及WeakSet

728 阅读2分钟

在看vue3源码的时候,看到WeakMapWeakSet,不是很明白,平时只用Set做数组去重。所以趁这个机会好好学一下,查缺补漏。

Set 和 Map 主要的应用场景在于 数据重组数据储存

Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构

Map

Map 区别与Object的最大的特点是,它能存储任意类型的key

举个例子:

let map = new Map()
let john = { name: 'John' }

map.set('1', 'str1') // a string key
map.set(1, 'num1') // a numeric key
map.set(true, 'bool1') // a boolean key
map.set(john, 123) // a object key

console.log(map.get(1)) // 'num1'
console.log(map.get('1')) // 'str1'
console.log(map.get(true)) // 'bool1'
console.log(map.get(john)) // 123
console.log(map.size) // 4

Map会保留key的类型,而Object会把key转换为stringObjectkey还可以是Symbol)。

属性和操作方法:

  • size属性:返回 Map 结构的成员总数
  • Map.prototype.set(key, value):向Map中添加或更新元素
  • Map.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined
  • Map.prototype.has(key):判断 Map 对象中是否有 Key 所对应的值,有返回 true,否则返回 false
  • Map.prototype.delete(key)delete方法删除某个键,返回true。如果删除失败,返回false
  • Map.prototype.clear():清除所有成员,没有返回值

遍历方法:

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的每个成员。

WeakMap

WeakMap 结构与 Map 结构类似,区别在于:

WeakMap的键名只能是对象,且是弱引用对象。 在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 是不能被遍历的。

Set

Set区别与数组的最大特点是成员无序且唯一,成员可以是任意类型。

Set 的属性、操作方法及遍历方法与Map类似

WeakSet

WeakSetSet 的区别是,它的成员都是对象且都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用。

应用

上面的学完之后,可以根据需要选择自己需要的数据结构。

下面介绍几种应用场景:

  1. 数组去重
Array.from(new Set([1, 2, 3, 1, 2])) // [1, 2, 3]
  1. 实现并集 (Union)、交集 (Intersect) 和差集
let set1 = new Set([1, 2, 3])
let set2 = new Set([4, 3, 2])

let intersect = new Set([...set1].filter(value => set2.has(value))) // Set {2, 3}
let union = new Set([...set1, ...set2]) // Set {1, 2, 3, 4}
let difference = new Set([...set1].filter(value => !set2.has(value))) // Set {1}
  1. WeakMap, vue3 源码 track函数收集依赖部分
// 是否应该收集依赖
let shouldTrack = true
// 当前激活的 effect
let activeEffect
// 原始数据对象 map
const targetMap = new WeakMap()
function track(target, type, key) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 每个 target 对应一个 depsMap
    targetMap.set(target, (depsMap = new Map()))
  }

  let dep = depsMap.get(key)
  if (!dep) {
    // 每个 key 对应一个 dep 集合
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    // 收集当前激活的 effect 作为依赖
    dep.add(activeEffect)
    // 当前激活的 effect 收集 dep 集合作为依赖
    activeEffect.deps.push(dep)
  }
}