官网解释
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值都可以作为键或值。
WeakMap
对象保存键值对,其中的键必须是对象,值可以是任意的类型,并且不会创建对它的键的强引用。换句话说,一个对象作为 WeakMap
的键存在,不会阻止该对象被垃圾回收。
使用区别
1.如果你想要可以获取键的列表,你应该使用 Map
而不是 WeakMap
。
2.如果你想要作为键的对象可以被垃圾回收,你应该使用 WeakMap
。
扩展
我平时工作根本就没用过weakMap,完全不知道它的优势在哪儿啊,面试的时候像背书背下来吗,面完就忘了,这知识学起来还有啥意义?
然后我就查了一下vue3源码,发现源码里面有用到weakMap,那不就可以研究一下vue3源码怎么使用的weakMap,解决了什么问题,面试的时候就可以拿这个当案例讲weakMap的使用场景;
既加深了对weakMap的理解,又加深了对vue3源码的理解,一箭双雕啊!
vue3对weakMap的使用
发现track方法里有用到
源码在packages/reactivity/src/effect.ts
js
//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
js
//只保留重要代码
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
js
//只保留重要代码
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
js
//只保留重要代码
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避免了内存泄漏问题