响应式对象与只读对象
Vue3 响应式系统的关键 API 与 rective相关的有以下四个:
-
reactive(): 创建深度响应式对象 -
readonly(): 创建只读响应式对象 -
shallowReactive(): 创建浅层响应式对象 -
shallowReadonly(): 创建浅层只读响应式对象
reactive 方法的实现
export function reactive<T extends object>(target: T): Reactive<T> {
// 如果目标对象已经是只读的,直接返回
if (isReadonly(target)) {
return target
}
// 创建响应式对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
主要实现原理
使用WeakMap存储原始对象到代理对象的映射:
export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const readonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReadonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
通过Proxy创建响应式对象:
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
核心方法createReactiveObject
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// 1. 检查目标是否为对象
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(target)}`)
}
return target
}
// 2. 检查目标是否已经是Proxy
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 3. 检查缓存中是否已存在对应的Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 4. 获取目标类型并验证
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 5. 创建新的Proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 6. 缓存Proxy
proxyMap.set(target, proxy)
return proxy
}
主要功能和步骤:
-
参数说明:
-
target: 要代理的目标对象
-
isReadonly: 是否是只读
-
baseHandlers: 基本类型的处理器
-
collectionHandlers: 集合类型的处理器
-
proxyMap: 缓存代理对象的WeakMap
-
主要操作流程:
-
类型检查:
-
检查目标是否为对象,非对象直接返回
-
开发环境下会发出警告
-
-
避免重复代理:
-
检查目标是否已经是代理对象
-
检查缓存中是否已有对应的代理
-
-
目标类型判断:
enum TargetType {
INVALID = 0, COMMON = 1, // Object 和 Array COLLECTION = 2 // Map、Set、WeakMap、WeakSet}
-
创建代理:
-
根据目标类型选择不同的handlers
-
使用Proxy API创建代理对象
-
将新创建的代理存入缓存
-
-
性能优化:
-
使用WeakMap作为缓存,避免内存泄漏
-
对同一个对象多次调用reactive会返回同一个代理实例
-
-
特殊处理:
-
对于Collection类型(Map/Set等)使用特殊的handlers
-
对于已经是代理的对象有特殊的处理逻辑
-
-
总结:createReactiveObject是Vue响应式系统的核心方法,它通过一系列的检查和优化,确保能够安全且高效地为对象创建响应式代理。它的实现考虑了性能优化、类型安全、边界情况等多个方面。
关键特性
-
深度响应式转换
-
自动解包嵌套的ref
-
对集合类型(Map/Set等)的特殊处理
-
缓存代理对象避免重复创建
-
只读代理的支持
关于 WeakMap
WeakMap 的特性:
// WeakMap示例
const weakMap = new WeakMap();
let obj = { data: "some data" };
// 设置键值对
weakMap.set(obj, "metadata");
// 如果obj被设为null
obj = null;
// obj作为键和对应的值都会被自动回收
与普通 Map 的区别:
// 普通Map
const normalMap = new Map();
let obj = { data: "some data" };
// 设置键值对
normalMap.set(obj, "metadata");
// 即使obj被设为null
obj = null;
// Map中的引用仍然存在,不会被垃圾回收
主要优势:
- 弱引用特性:
// WeakMap中的键是弱引用
const wm = new WeakMap();
{
const obj = { id: 1 };
wm.set(obj, "data");
} // obj离开作用域后会被回收
- 自动垃圾回收:
let obj = { id: 1 };
const wm = new WeakMap();
wm.set(obj, "data");
obj = null; // 对象可以被垃圾回收
内存泄漏的防范:
// 不会造成内存泄漏的情况
const wm = new WeakMap();
function process(obj) {
wm.set(obj, "processed");
// 函数结束后,如果obj没有其他引用
// WeakMap中的数据会被回收
}
使用场景:
// 适合用于关联数据存储
class DOMTracker {
private elementData = new WeakMap();
track(element: HTMLElement, data: any) {
this.elementData.set(element, data);
// 当element被删除时,数据会自动清理
}
}
总结WeakMap避免内存泄漏的原因:
1. 弱引用机制:
- WeakMap的键是弱引用
- 不会阻止垃圾回收器工作
- 当对象只被WeakMap引用时可以被回收
- 自动清理:
- 不需要手动清理数据
- 避免了忘记清理导致的内存泄漏
- 更安全的内存管理
- 适用场景:
- 临时数据关联
- 缓存实现
- DOM元素关联数据
- Vue中的应用:
- 存储原始对象到代理对象的映射
- 当组件销毁时自动清理
- 避免长期运行导致的内存累积