likes
comments
collection
share

前端面试题:说一下map和weakMap的区别

作者站长头像
站长
· 阅读数 5

官网解释

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值都可以作为键或值。

WeakMap 对象保存键值对,其中的键必须是对象,值可以是任意的类型,并且不会创建对它的键的强引用。换句话说,一个对象作为 WeakMap 的键存在,不会阻止该对象被垃圾回收。

使用区别

1.如果你想要可以获取键的列表,你应该使用 Map 而不是 WeakMap

2.如果你想要作为键的对象可以被垃圾回收,你应该使用 WeakMap

扩展

我平时工作根本就没用过weakMap,完全不知道它的优势在哪儿啊,面试的时候像背书背下来吗,面完就忘了,这知识学起来还有啥意义?

然后我就查了一下vue3源码,发现源码里面有用到weakMap,那不就可以研究一下vue3源码怎么使用的weakMap,解决了什么问题,面试的时候就可以拿这个当案例讲weakMap的使用场景;

既加深了对weakMap的理解,又加深了对vue3源码的理解,一箭双雕啊!

vue3对weakMap的使用

发现track方法里有用到

源码在packages/reactivity/src/effect.ts

//weakMap主要是用来存储{target -> key -> dep}的关系
//通过使用weakMap可以减少内存的开销
//注意targetMap是全局的
const targetMap = new WeakMap<object, KeyToDepMap>()

//只保留重要代码
export function track(target: object, type: TrackOpTypes, key: unknown) {
    //depsMap可以看成是一个依赖管理器
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
}

track意思是追踪,就是用来收集依赖的,依赖可以简单看成用来更新组件的对象effect,那track在什么时候用到了?

源码在packages/reactivity/src/reactive.ts

//只保留重要代码
export function reactive(target: object) {
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
//只保留重要代码
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  const proxy = new Proxy(
    target,
    baseHandlers
  )
  return proxy
}

发现是reactive方法,reactive里又用到了baseHandlers做代理操作

baseHandlers是个对象,是BaseReactiveHandler类的一个实例,提供了get方法,用来对target作代理操作,vue3跟vue2一样,都是在get中收集依赖,在set中通知依赖更新,那么get方法里肯定用到了track方法,查找源码发现果然如此

源码地址packages/reactivity/src/baseHandlers.ts

//只保留重要代码
class BaseReactiveHandler implements ProxyHandler<Target> {
  get(target: Target, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver)
    track(target, TrackOpTypes.GET, key)
    return res
  }
}

那么路径大致就是 reactive->createReactiveObject->BaseReactiveHandler.get->track->WeakMap

reactive我们都比较熟悉,vue3通过reactive可以将一个对象变成响应式的,那个target就是传入的对象,每个target都关联一个依赖管理器,依赖管理器里一般都会保存一个渲染effect用来渲染组件

整体流程我们梳理一下

1.reactive将我们传入的对象target转化成响应式的,会返回一个proxy代理对象,并在get中收集依赖,保存到依赖管理器里

2.当reactive对象被修改后,会在set中通知依赖管理器更新依赖,依赖更新触发组件重新渲染

因为track收集依赖和trigger通知依赖更新都需要快速拿到依赖管理器,所以我们需要一个以target为key,依赖管理器为value的map来快速拿到依赖管理器

那为什么要用weakMap呢?

推测一下应该是reactive对象的生命周期是跟组件绑定的,reactive对象是target的代理对象,当组件销毁后target对象就没用了,如果使用map的话,因为map里的键是target对象,会导致target对象无法被垃圾回收,从而有可能造成内存泄漏

那最后只要验证一下reactive对象的生命周期是和组件绑定即可,继续查找源码

源码packages/runtime-core/src/component.ts

//只保留重要代码
export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
    instance.setupState = proxyRefs(setupResult)
}

instance是组件实例对象,setupResult就是setup函数返回的对象,里面包含了reactive对象(target的代理对象),也就是说reactive对象被挂载到了组件实例上,两者的生命周期是一致的。

使用weakMap在组件运行期间,target对象会被垃圾回收吗?

不会,因为只要instance组件实例存在,那么target对象就不会被垃圾回收器回收,因为可以通过instance.setupState访问到reactive对象,从而访问到target对象

总结

weakMap对象保存键值对,其中的键是对象,而且是弱引用,可以被垃圾回收机制回收

使用场景

vue通过reactive方法对target对象做响应式处理的时候,需要通过map对象保存target和依赖管理器之间的关系,

当获取target对象中的属性的时候,依赖管理器会收集依赖

当更新target对象中的属性的时候,依赖管理器会通知依赖更新,从而触发渲染

组件销毁后,target对象也应该销毁,如果使用map,因为map是全局的,会导致target无法被垃圾回收器回收

如果频繁的创建和销毁组件,那么就会产生大量无法被销毁的target对象,最终导致内存泄漏

使用weakMap替代map,组件销毁后,垃圾回收器就能回收这些target对象,从而减少内存的开销

所以weakMap还是很有用的,vue3通过weakMap避免了内存泄漏问题

Map

WeakMap

转载自:https://juejin.cn/post/7346527007280693263
评论
请登录