前言#
前面我们分析完了大体上的流程,还剩下部分流程需要搭配api来分析。
那么从今天开始,我们开始着手分析响应式相关的知识。
建议搭配:
坏蛋Dan:vue runtime源码分析学习——day8:patch打补丁part4:processComponent处理组件
里面对任务调度的分析,这样比较顺。
开始之前#
响应式原理的量也很多,东西也很核心,所以我们这里需要拆分为好几块内容来分析。
我们今天先来分析ref和reactive两个api做了什么,分析完了应该就可以和前面流程分析的内容对接上了。
另外记住一句话:写(set)之前一定会读(get)。不可能存在读之前就写的行为,从逻辑上就说不通,不知道写的对象怎么写入?
ref#
我们编译阶段就接触过很多ref相关的东西了,比如语法糖$ref等。用法这里就不展示了,相信大家都懂。
我们直接来看下代码
代码位于packages\reactivity\src\ref.ts里面
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}上来几个函数重载,然后调用createRef方法创建ref再返回。
isRef没啥好说的,就是判断这个变量指向的值是否是一个ref包裹的值,关键字是__v_isRef,为true则表示这个是一个ref包裹的值。
然后我们来看下这个RefImpl类。
RefImpl
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value它有两个私有属性_value和_rawValue,两个公有字段dep和__v_isRef,其中__v_isRef只读。
constructor中根据__v_isShallow表示是否是浅(shallow)跟踪,浅也就是不做响应式处理,不去跟踪数据变化。
toRaw:就是标记这个数据为ReactiveFlags.RAW,也就是__v_raw。这个ReactiveFlags我们之前见过几次,应该是用来标记一个数据的类型的,比如__v_skip的数据可以不用跟踪。
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.IS_SHALLOW]?: boolean
[ReactiveFlags.RAW]?: any
}reactive:这货就先不分析了,之后分析reactive的时候再说。只有是对象的时候才会reactive包裹处理。
它还有两个属性,也就是getter/setter。
其中getter返回的是_value,所以我们外部data.value = xx的value就是这个value。
这个trackRefValue方法我们等下再分析,比较重要,涉及到跟踪收集watcher。
我们先看下setter做了什么。
isShallow:就是判断这个数据是否是浅的isReadonly:判断这个数据是否是只读的hasChanged:Object.is[2] 用来判断这个数据是否相等。
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue)需要注意比对对象是===

如果新的值和旧的不同,将新的值传递给_value这个私有属性中,注意每次set的时候都会判断这个值的属性,然后调用toReactive处理一次,避免这个值在某次过程中类型变了导致达不到预期。
这两个方法做的事情和常规的getter/setter差不多,就是在返回值/设置值。
但是和常规不同的是,还多了一步trackRefValue/triggerRefValue。
那么我们现在就来看下这个trackRefValue和triggerRefValue做了什么。
trackRefValue#
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}activeEffect:这个变量我们前面接触过,前面我们分析processComponents那篇文章中就有说到,effect.run的时候将这个activeEffect指向了effect它自己。dep:我们之前在effect那里说过这个字段,就是用来存放依赖的。它是一个集合,有两个字段w, n。w是wasTracked表示曾经跟踪过的依赖,而n是newTracked表示新跟踪的依赖。trackEffects:看名字是跟踪effect的,我们来看下它的代码
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0maxMarkerBits:前面的文章中也有说到,这里再说下,跟踪递归的深度是按位的,最高30位。effectTrackDepth:当前effect被递归跟踪的次数。wasTracked:表示曾经跟踪过,&按位与运算,只有两个都是1的时候才会是1nowTracked:同上。
如果还没达到30位,那么判断它是否以前跟踪过,如果没跟踪过就把它放到deps里。
如果超出30位了,执行清空模式也就是cleanupEffect,将deps归零。我们前面的文章中分析过这个方法了,这里就不多说了。
这块逻辑最重要的点是
dep.add(activeEffect!)
activeEffect!.deps.push(dep)这让这个数据搭上了任务调度的车。
现在我们可以在set的时候去通知deps里的effect们去更新(run)了。
如果xxx是ref包裹后的数据,那么任何代码涉及到xxx.value都会触发get value,也就会被跟踪,这就完成了自动收集。
triggerRefValue#
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
if (__DEV__) {
triggerEffects(ref.dep, {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal
})
} else {
triggerEffects(ref.dep)
}
}
}这里不多说,直接来看下triggerEffects方法做了什么
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}就和我在上面说的一样,这里拿到deps里的effect,然后通知它们去执行scheduler或者 run。
scheduler对于实际上也是触发instance.update方法,但是它是走scheduler的也就是被放到任务调度队列里执行,按顺序。
run则是instance.update触发的方法,会去重新执行render function和触发patch然后重新渲染。
注意这里执行的条件是effect !== activeEffect || effect.allowRecurse,要么不是当前activeEffect,要么允许递归。
为什么是activeEffect不行呢?因为我们前面分析过effect.run的代码,会把activeEffect指向effect自己,所以如果这个时候再去触发effect.run,那么就是递归操作了,这也就是allowRecurse允许执行的原因。
另外这里貌似还有和computed相关的逻辑,这里就暂时不分析了,后面单独分析。
总结ref#
那么ref我们就分析完了,经过前面知识的铺垫,我们分析起来是比较轻松的。
核心就是get和set,当任何执行中的代码涉及到xx.value表达式的时候,这个ref实例xx的get value代码就会被触发到,然后触发trackRefValue方法,将当前activeEffect收集到ref.deps里面。
这样就完成了自动收集。
而当任何执行中的代码中涉及到xx.value = xxx时,set value就会被触发(在这表达式之前先涉及到了xxx.value表达式,所以先是触发了trackRefValue),这个时候就会触发triggerRefValue,去触发前面收集到的dep,也就是effect,然后执行它们的scheduler或者run。
scheduler走的是任务调度,队列执行到它之后才会执行run方法。
而run方法接着就触发updateComponentFn方法,然后触发render function的执行和patch。
这样就更新了DOM,更新了界面。
reactive#
代码位置:packages\reactivity\src\reactive.ts
/**
* Creates a reactive copy of the original object.
*
* The reactive conversion is "deep"—it affects all nested properties. In the
* ES2015 Proxy based implementation, the returned proxy is **not** equal to the
* original object. It is recommended to work exclusively with the reactive
* proxy and avoid relying on the original object.
*
* A reactive object also automatically unwraps refs contained in it, so you
* don't need to use `.value` when accessing and mutating their value:
*
* ```js
* const count = ref(0)
* const obj = reactive({
* count
* })
*
* obj.count++
* obj.count // -> 1
* count.value // -> 1
* ```
*/
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
} 这里没啥好说的,我们先来看下createReactiveObject方法做了什么
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
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] &&
!(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 === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}只有对象类型的数据可以reactive
如果这个数据的类型是RAW,那么表示这个数据不需要响应式处理。
proxyMap是用来存储reactive对象的,它是一个weakMap[4] ,简单的说weakMap建立的是一个weak的引用,这意味着不影响垃圾回收。
如果这个数据已经在map里了,那就直接返回即可。
然后我们来看下getTargetType的代码
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
export const toTypeString = (value: unknown): string =>
objectToString.call(value)
export const toRawType = (value: unknown): string => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1)
}只有特定的几个对象类型才可以被reactive。
Object.isExtensible[5] :用来判断这个对象是否可以插入新的property。
然后回到前面的代码中,如果不是符合的类型都会被标记为Invalid,直接return。
然后创建一个新的Proxy[6]实例。
最后把这个代理对象存放在weakMap里面,接着return出去。
这就是reactive整个流程,没啥好说的,我们来分析前面跳过的baseHandlers,它里面的东西才是重点。
baseHandlers#
实际上这个参数有好几个对应的数据,比如从shallowReactive进来的shallowReactiveHandlers,但是我们这里只分析从reactive方法里面进来的baseHandlers。
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}这里面的方法我们一个一个来分析。
get#
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
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)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
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 (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// 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
}
}这个方法需要先知道的是在重写proxy实例的get[7]方法。
Proxy.get搭配Reflect.get我放到下面去分析了。
我们来看下arrayInstrumentations
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
const arr = toRaw(this) as any
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
const res = arr[key](...args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return arr[key](...args.map(toRaw))
} else {
return res
}
}
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
pauseTracking()
const res = (toRaw(this) as any)[key].apply(this, args)
resetTracking()
return res
}
})
return instrumentations
}简单的说就是在重写数组的一些方法,让这些方法在执行的过程中可以收集依赖变成响应式以及触发依赖的update。
includes[8]、indexOf[9]、lastIndexOf[10]和push[11]、pop[12]、shift[13]、unshift[14]、splice[15]它们都是敏感的数组操作.
在getter这里仅用于查询/访问等,这个时候做收集即可。
我们稍微看下track方法做了什么。
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}做的事情很简单,就是判断这个数据是否被跟踪了,没有就加到weakMap里,然后给它存储依赖。
而后面五个可能会对数组自身造成影响。
所以在访问它们的时候需要停止收集,避免存在无限循环的问题。
简单地说createArrayInstrumentations这个方法只是在重写这个数组的几个方法。
我们回到createGetter方法里面
buildInSymbols:这个不看代码了,是为了避免ios10.x的一个兼容性写法。isNonTrackableKeys这个方法就不看代码了,就是__proto__,__v_isRef,__isVue这仨类型。track:前面分析了
剩下的其实也都没啥好说的了,如果是浅跟踪,直接把Reflect.get得到的数据返回出去,而如果是ref包裹后的数据,那么判断数据是否是一个数组,是的话直接返回元素,否则都是返回res.value。而如果是object类型,判断是否是需要只读,是的话处理的时候会将readonly置为true。保底把res返回。
简单的说,这个方法就是通过代理访问来收集依赖以及改写数组的方法等。
set#
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}这个方法中做的事情也挺简单,最重要的点就是Reflect.set[17]和trigger。
注意这里没必要去receiver里拿key对应的值,因为set阶段的目标就是target,不管它是从哪里来的,如果target没有就给它新set进去。
set完之后调用trigger方法去触发任务调度更新组件。
我们来看下trigger方法
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}targetMap前面说过了,就是用来存放响应式数据的集合
如果响应式集合中没有找到这个数据,那就说明这个数据是新来的,直接return,毕竟连依赖都没有,自然就没必要去触发任务调度。
如果触发的状态是CLEAR,那么这个时候一棍打死,全部触发。
如果访问的key是数组的length,那么收集对length有依赖的effect以及index值大于newValue的index值有依赖的effect。
这里可能有些拗口,举个例子, array.splice(1, 0, 'a'),我们插入了一个元素,这个元素会导致这个元素后面的元素index发生变化,这个时候就需要收集对它们有依赖的effect,而这个插入元素之前的并不需要收集,因为没有发生改变。
我们来写个demo测试下

打上断点

然后直接F5reload下

可以看到key值就是index
没有录下来,就是set修改某个元素之后触发的逻辑,主要是知乎不允许上传较大的图,所以拆开来再录一次。

可以看到从2开始往后的元素都被影响到了
ok,回到我们的代码中。
不同的方法进来的数据会被标记为不同的类型,比如push方法会被标记为Add,pop则是delete,而splice、shift、unshift三个方法则不同,它们不仅可以操作最后面的元素,还可以操作其它地方的元素,比如头部。这个时候的逻辑就不一样了,它们的触发会导致别的元素也跟着被触发,所以它们是一系列的。
splice看是否是插入,插入则从插入的地方开始一路开始替换,也就是set,到了最后一个元素发现比原来的长,那么就按add插入。删除相反最后发现变短了就delete。shift:直接后一个替换set前一个,最后一个发现长度变短了,那就删掉delete。和splice的删除一样。unshift:和splice的插入一样,一路set替换到最后一个,发现变长了,就插入add。
我上面的gif图里就是这个操作。
然后把这个key对应的依赖都先拿出来再说。
Add:分两种情况,要么是数组,要么是集合(collection),比如map这种,这俩种类型虽然像,但是在其它变成语言中是有区分的,集合存储在堆上,长度可变;数组则是存储在栈上,长度不可变。但是它俩都能迭代。而在前端中则没这么多限制。扯远了,回到代码中。如果是数组并且key是整数,那么直接将所有依赖length的effect都给触发了,因为数组的长度发生了变化。而如果不是数组,那么一般是map、set等,当然还有给对象新增属性也算是add,这个时候获取依赖这个类数组的数据的effect,ITERATE_KEY和MAP_KEY_ITERATE_KEY就是两个symbol,暂时不清楚是干啥的。Delete:如果你用了delete关键字去删除对象的一个属性,那么也算是这个。数组在这里就不需要再收集其它的东西了。Set:map类型需要单独把ITERATE_KEY这个symbol对应的依赖拿出来。
拿完需要触发的effect之后,开始执行执行这些个effect。
triggerEffects这个方法前面说过了,这里就不多说了。最后是会触发effect.run,而run会去触发updateComponentFn方法触发patch,最终引起视图的重新渲染。
这个方法简单地说就是在收集需要更新的effect,然后触发它们的run,最终引起视图的重新渲染。
后面单独出篇文章整理下这块的内容,毕竟分析的过程比较散。
那么setter这块内容就说完了,和我们之前想的一样,就是在收集需要更新的effect,然后触发它们的run。
deleteProperty#
function deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key)
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}这个方法就比较简单了,就不分析了,简单地说就是删除对象的属性等会直接触发这个方法,然后触发trigger,后面就不用多说了。
has#
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}这个方法就是查看某个数据是否存在这个字段/方法,这里做收集即可。
同样它不需要receiver,查的就是target,也就是调用这个方法的对象。
ownKeys#
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}同上,也是针对target自身。
补充#
Proxy.get#
这个方法简单的说就是返回当前代理对象target的key指向的值,但是它有三个参数

根据描述,第三个参数是指向proxy实例或者指向继承了该proxy的对象。
我们来实验下
const obj = {
a: 123,
b: 456
}
const proxy = new Proxy(obj, {
get (target, key, receiver) {
console.log(target, key, receiver);
return Reflect.get(target, key, receiver);
}
})
let c = proxy.b;
console.log('the c is', c);然后看下结果

target:指向被代理的对象,这一点是可以确定的key:不多说receiver:这时的receiver执行proxy自身,也没啥问题。
那么我们修改一下代码
const obj = {
a: 123,
b: 456
}
const proxy = new Proxy(obj, {
get (target, key, receiver) {
console.log(target, receiver)
return target[key];
return Reflect.get(target, key, receiver);
}
})
const proxy2 = {};
Object.setPrototypeOf(proxy2, proxy);
console.log(proxy2);
let c = proxy2.b;
console.log('the c is', c);重新运行的时候会发现target指向了obj,而receiver指向了proxy2

这个时候指向就有问题了,target或者说是this不再指向调用它的那个proxy2。
如果这个时候你还利用了target做其它操作,或者说你这个数据本身就依赖于自身的另一个数据,比如下面这样
const obj = {
a: 123,
b: 456,
get d () {
return this.b
}
}
const proxy = new Proxy(obj, {
get (target, key, receiver) {
// console.log(target, receiver);
return target[key];
return Reflect.get(target, key, receiver);
}
})
const proxy2 = {
b: 0
};
Object.setPrototypeOf(proxy2, proxy);
console.log(proxy2);
let c = proxy2.d;
console.log('the c is', c);
c的预期打印结果应该是0,但是实际上却是456。
所以receiver实际上的作用就是正确的指向调用这个key的对象。
这个时候我们可以搭配上Reflect.get方法。
Reflect.get#
我们把上面的代码改成Reflect.get的方式
const obj = {
a: 123,
b: 456,
get d () {
return this.b
}
}
const proxy = new Proxy(obj, {
get (target, key, receiver) {
// console.log(target, receiver);
// return target[key];
return Reflect.get(target, key);
}
})
const proxy2 = {
b: 0
};
Object.setPrototypeOf(proxy2, proxy);
console.log(proxy2);
let c = proxy2.d;
console.log('the c is', c);

实际上还是没变,为什么呢?因为我们没有传第三个参数。
我们再调整下代码
// ...
return Reflect.get(target, key, receiver);
// ...然后重新运行下

现在终于对了。
前面我们说过这个receiver指向的对象可能和target一样,也可能不一样,但是它一定是正确的指向,所以这个时候只要我们执行这段代码的时候指向置为receiver就完事了。
类似于target[key].call(receiver)这种操作。
总结#
ref和reactive是有区别的,ref非对象是不走proxy逻辑的,这一点需要注意。
核心就是getter收集依赖,setter触发依赖。
这段时间整个人都处于焦虑和无力。
无力是来自可能到来的技术革命。
焦虑是很不幸我大概率是革命的受害者。。。
现在基本没有动力去写文章了,因为我不知道还坚持在这个赛道到底还有没有出路。。。
简单的总结下我自己的想法:49年guo军。。。
参考#
- ^ref https://vuejs.org/api/reactivity-core.html#ref
- ^Object.is https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
- ^reactive https://vuejs.org/api/reactivity-core.html#reactive
- ^weakMap https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
- ^Object.isExtensible https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
- ^Proxy https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- ^abproxy.get https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
- ^includes https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
- ^indexOf https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
- ^lastIndexOf https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
- ^push https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
- ^pop https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
- ^shift https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
- ^unshift https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
- ^splice https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
- ^Proxy.set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
- ^Reflect.set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
- ^Proxy.deleteProperty https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
- ^Proxy.has https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
- ^Proxy.ownKeys https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
- ^Reflect.get https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
发布于 2023-03-22 15:35・IP 属地广东
