- A+
源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts
baseHandler
用于处理对象、数组类型的getter
和setter
。
这个文件中主要有两个函数,和三个类。
arrayInstrucmentations
和hasOwnProperty
这两个函数主要起到辅助作用;
3个类:
BaseReactiveHandler
:负责处理getter
;MutableReactiveHandler
和ReadonlyReactiveHandler
:负责处理setter
;
依赖
依赖比较零碎,大致分为以下几种:
- 用于判断数据类型;
Vue
内置的Flag
或Type
,用来标记对象或者操作的类型;- 暂停与重置依赖追踪和任务调度的方法;
- 其它零碎的比如:
warning
:在控制台输出警告信息;makeMap
:传入一个用,
分隔的多个key
组成的字符串,返回一个has
函数用于检查后续传入的key
是否存在于一开始传入的字符串中。
arrayInstrucmentations
arrayInstrumentations
这个对象用于记录一些处理过的数组方法(拦截操作),通过createArrayInstrumentations
构建后大概长这样:
{ ... 'push': function(this, ...args){...}, 'indexOf': function(this, ...args){...}, ... }
拦截这些方法的原因:
-
['includes', 'indexOf', 'lastIndexOf']
:数组中可能包含响应式对象,这几个方法是需要比较数组元素的,直接比较可能会出错,因此需要拦截这些方法,在比较的过程中考虑使用toRaw
转成原始对象进行比较。 -
['push', 'pop', 'shift', 'unshift', 'splice']
:这些方法会改变数组的长度length
属性,从而触发与length
属性相关的effect
,而effect
中如果又有这些方法,那么就会导致死循环。因此,这些方法需要被拦截做特殊处理,在执行这些方法的时候要暂停依赖的追踪和调度。
源码与注释:
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations() function createArrayInstrumentations() { const instrumentations: Record<string, Function> = {} // 给需要处理可能包含响应式值的数组方法增加拦截逻辑 ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { // 将 this 转换为原始数组 const arr = toRaw(this) as any // 追踪数组每个元素的 GET 操作 for (let i = 0, l = this.length; i < l; i++) { track(arr, TrackOpTypes.GET, i + '') } // 先使用原始参数(可能是响应式的)运行原方法 const res = arr[key](...args) // 如果结果是 -1 或 false,说明没有找到或不匹配,再次使用原始值运行一次 if (res === -1 || res === false) { return arr[key](...args.map(toRaw)) } else { return res } } }) // 拦截会改变数组长度的方法,避免长度变化被追踪,从而防止出现无限循环的问题 (#2137) ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { // 暂停追踪 pauseTracking() // 暂停调度 pauseScheduling() // 使用原始数组运行原方法 const res = (toRaw(this) as any)[key].apply(this, args) // 重置调度 resetScheduling() // 重置追踪 resetTracking() return res } }) return instrumentations }
hasOwnProperty
Vue对hasOwnProperty
做了特殊处理,主要在于处理以下问题:
- 确保需要检查的
key
只能是Symbol
类型或string
类型,如果是其它,则通过String()
转为字符串; - 使用
toRaw
在原始对象上查询key
是否存在; - 使用
track
追踪key
;
源码:
function hasOwnProperty(this: object, key: unknown) { // #10455 hasOwnProperty may be called with non-string values if (!isSymbol(key)) key = String(key) const obj = toRaw(this) track(obj, TrackOpTypes.HAS, key) return obj.hasOwnProperty(key as string) }
BaseReactiveHandler
这个类主要负责配置getter
,当reactive
API包装的响应式对象的某个key被读取时,会触发这里的getter
:
- 如果读取的
key
是内置的ReactiveFlags
,返回相应的值; - 如果
target
是一个数组,那么需要应用上述arrayInstrucmentations
记录的处理过的数组; - 如果
key
是hasOwnProperty
,返回上述特殊处理过的hasOwnProperty
; - 对
key
记录依赖。
class BaseReactiveHandler implements ProxyHandler<Target> { constructor( protected readonly _isReadonly = false, // 是否只读 protected readonly _isShallow = false, // 是否浅层响应式 ) {} get(target: Target, key: string | symbol, receiver: object) { const isReadonly = this._isReadonly, isShallow = this._isShallow // 处理 ReactiveFlags 特殊标志 if (key === ReactiveFlags.IS_REACTIVE) { // 判断目标是否是响应式的 return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { // 判断目标是否是只读的 return isReadonly } else if (key === ReactiveFlags.IS_SHALLOW) { // 判断目标是否是浅层响应的 return isShallow } else if (key === ReactiveFlags.RAW) { // 处理 RAW 标志 if ( receiver === (isReadonly ? isShallow ? shallowReadonlyMap : readonlyMap : isShallow ? shallowReactiveMap : reactiveMap ).get(target) || // receiver 不是响应式代理,但具有相同的原型 // 这意味着 receiver 是响应式代理的用户代理 Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) ) { return target } // 提前返回 undefined return } const targetIsArray = isArray(target) // 处理非只读的情况 if (!isReadonly) { if (targetIsArray && hasOwn(arrayInstrumentations, key)) { // 对于数组的特殊方法,使用上述的方法拦截处理 return Reflect.get(arrayInstrumentations, key, receiver) } if (key === 'hasOwnProperty') { // 特殊处理 hasOwnProperty 方法 return hasOwnProperty } } // 默认的 Reflect.get 操作 const res = Reflect.get(target, key, receiver) // 处理内置符号和非可追踪的键 if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res } // 处理非只读情况,执行追踪操作 if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // 处理浅层响应情况 if (isShallow) { return res } // 处理 Ref 类型的值 if (isRef(res)) { // 如果是数组并且键是整数,则跳过 unwrap return targetIsArray && isIntegerKey(key) ? res : res.value } // 处理对象类型的值,将返回的值转化为代理对象 if (isObject(res)) { // 将返回的值转换为代理对象,避免循环依赖 return isReadonly ? readonly(res) : reactive(res) } return res } }
MutableReactiveHandler
这个类实现了对set
、deleteProperty
、has
、ownKeys
的拦截
class MutableReactiveHandler extends BaseReactiveHandler { constructor(isShallow = false) { super(false, isShallow) // 调用父类构造函数,设置只读标志为 false } set( target: object, key: string | symbol, value: unknown, receiver: object, ): boolean { let oldValue = (target as any)[key] // 获取目标对象中原有的值 if (!this._isShallow) { // 如果不是浅层响应,进行深层处理 const isOldValueReadonly = isReadonly(oldValue) // 判断原有值是否是只读的 if (!isShallow(value) && !isReadonly(value)) { oldValue = toRaw(oldValue) // 获取原有值的原始对象 value = toRaw(value) // 获取新值的原始对象 } // 如果原有值是 ref 类型并且新值不是 ref 类型 if (!isArray(target) && isRef(oldValue) && !isRef(value)) { if (isOldValueReadonly) { // 如果原有值是只读的,返回 false return false } else { oldValue.value = value // 更新 ref 的值 return true } } } else { // 在浅层模式中,直接设置对象,不考虑其是否为响应式 } // 判断目标对象是否之前已经有这个键 const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) const result = Reflect.set(target, key, value, receiver) // 使用 Reflect 设置值 // 判断target是否是实际被修改的对象 if (target === toRaw(receiver)) { if (!hadKey) { // 如果之前没有这个键,触发 ADD 操作 trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // 如果键已经存在且新值不同,触发 SET 操作 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result } deleteProperty(target: object, key: string | symbol): boolean { const hadKey = hasOwn(target, key) // 判断目标对象是否有这个键 const oldValue = (target as any)[key] // 获取原有值 const result = Reflect.deleteProperty(target, key) // 使用 Reflect 删除属性 if (result && hadKey) { // 如果删除成功且目标对象之前有这个键,触发 DELETE 操作 trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) } return result } has(target: object, key: string | symbol): boolean { const result = Reflect.has(target, key) // 使用 Reflect 检查是否存在该键 if (!isSymbol(key) || !builtInSymbols.has(key)) { // 如果键不是内置符号,追踪 HAS 操作 track(target, TrackOpTypes.HAS, key) } return result } ownKeys(target: object): (string | symbol)[] { // 追踪 ITERATE 操作,用于获取对象的所有键 track( target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY, ) return Reflect.ownKeys(target) // 使用 Reflect 获取对象的所有键 } }
解析:set
里的trigger
时机:
// 判断target是否是实际被修改的对象 if (target === toRaw(receiver)) { if (!hadKey) { // 如果之前没有这个键,触发 ADD 操作 trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // 如果键已经存在且新值不同,触发 SET 操作 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } }
如果target !== toRaw(receiver)
说明此时不是在对象上修改它本身的属性,而是通过原型链上的其它对象。这种情况不会触发更新。
ReadonlyReactiveHandler
这个类比较简单,主要是拦截set
和deleteProperty
这两个会改变对象的操作。
开发模式下会在控制台输出警告。
注意set
和deleteProperty
操作会返回true
,这是为了符合Proxy
规范:即使某些操作被拦截并不实际改变对象的状态,仍然需要返回一个布尔值以指示操作的成功或失败。
class ReadonlyReactiveHandler extends BaseReactiveHandler { constructor(isShallow = false) { super(true, isShallow) // 调用父类构造函数,将 isReadonly 设置为 true,表示对象是只读的 } set(target: object, key: string | symbol, value: unknown): boolean { if (__DEV__) { warn( `Set operation on key "${String(key)}" failed: target is readonly.`, target, ) } return true // 返回 true,表示设置操作被忽略 } deleteProperty(target: object, key: string | symbol): boolean { if (__DEV__) { warn( `Delete operation on key "${String(key)}" failed: target is readonly.`, target, ) } return true // 返回 true,表示删除操作被忽略 } }