大家好,我是前端小张同学,最近在学习vue时,偶然发现一个问题,reactive定义的对象数据,解构完成之后,那个对象居然还是 Proxy代理对象,并不是一个普通对象,当时就匪夷所思,why? 于是我又去看了一下源码,终于得出了答案。
话不多说,直接上源码。
1:reactive是怎么做拦截处理的?
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) { // 如果不是一个对象 则 返回当前 traget
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] && // 如果target 已经是一个 代理对象 则 返回当前对象
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
// 重点 在这个 targetType 会进行 选择不用的 handles
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
重点:
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
这段代码决定了,传入的object会被以什么样的形式去做拦截,vue提供了两种形式
mutableHandlers基本类型的拦截器mutableCollectionHandlers针对子集合Map 、WeakMap、set、weakSet类型 提供的拦截器
1.1:mutableHandlers 实现
源码路径: core\packages\reactivity\src\baseHandlers.ts
1.2:mutableCollectionHandlers实现
源码路径:core\packages\reactivity\src\collectionHandlers.ts
好,接下来,我将一步一步带着你看 get 的实现,有啥差异,请随我一起!!!
2: mutableHandlers get 实现
源码路径:core\packages\reactivity\src\baseHandlers.ts
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
// 重写 get访问器 返回一个 新的 get函数
return function get(target: Target, key: string | symbol, receiver: object) { //传入 目标对象 , 访问的 key 和 receiver 代理对象
if (key === ReactiveFlags.IS_REACTIVE) { // 判断 当前访问的对象的key 是不是一个 响应式的值 如果是 则返回 true 不用操作,说明已经代理过
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) { // 如果是一个只读的属性 则 直接返回 true 无需操作
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target) // target 是否是一个数组类型数据
if (!isReadonly) { // 判断是否是 只读 属性 如果非只读的属性 说明可以进行操作
if (targetIsArray && hasOwn(arrayInstrumentations, key)) { // 是数组 并且 当前属性 是本身的属性 则 通过key去获取
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
const res = Reflect.get(target, key, receiver) // 通过 Reflect 获取 target 目标对象的值
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { // 判断 访问的 对象是否是 Symbol , 是的话 并且set集合中存在 当前 symbol 的值 则返回
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (shallow) { //
return res
}
if (isRef(res)) { // 如果是 ref 则自动解包获取 Value
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) { // 如果是 object 则 通过 reactive 递归实现 代理
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res //经过上面的步骤 所有情况都已经处理完成 最终将 处理完的 目标对象 返回
}
}
总结一下 createGetter 干了什么?
- 返回一个 get 函数
- 边界处理,可以直接返回 target的情况,无需操作
- 判断target 是否是数组类型,数组类型,则重写实现数组方法,进行原始参数调用
- 判断 访问的 对象是否是 Symbol , 是的话 并且set集合中存在 当前 symbol 的值 则返回
- 判断 获取的目标对象 是不是 一个 ref,如果是 则 自动解包
- 如果是 对象 则 通过 reactive 进行递归代理。
- 经过上面的步骤 所有情况都已经处理完成 最终将 处理完的 目标对象 返回
3:mutableCollectionHandlers get实现
处理 Map 、WeakMap、set、weakSet 这种特殊情况实现就比较容易了,边界处理 + 获取对象值
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
3:总结
所以现在知道了,为什么 reactive 解构完成的对象,还具备响应式,并且 pinia 中的 state 定义的 对象类型,通过解构 state 也具响应式, 是因为被深度递归代理,所以这也是reactive的一部分实现,在面试中也比较重要,希望能给大家带来收获。
加入我们的开发群,添加我微信,备注加群,进入我们内部交流群,了解更多前端知识。