vue3的响应系统分析
前言
参考代码版本:vue 3.2.37
官方文档:https://vuejs.org/
vue3
的响应式处理主要集中在packages/reactivity/src/effect.ts
文件中。
effect
在vue3
中,会使用一个effect
方法注册副作用函数。为什么要注册副作用函数呢?
如果响应式数据更新,我们希望副作用函数中的相关数据也能同步更新。要实现这种效果,就需要我们做两个工作:
- 在读取响应式数据时,收集副作用函数。
- 在设置响应式数据时,触发副作用函数。
那么我们如何在设置响应式数据时,触发相关的副作用函数呢?这就需要我们在收集副作用函数时,使用某种数据结构把他暂存起来,等到需要到他的时候,就可以取出来。
effect
的作用就是将我们注册的副作用函数暂存。下面我们来看effect
的实现:
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
effect
可以接收两个参数,其中第二个参数为可选参数,可以不传。第一个参数是一个副作用函数fn
,第二个参数是个对象,该对象可以有如下属性:
-
lazy
:boolean
,是否懒加载,如果是true
,调用effect
不会立即执行监听函数,需要用户手动执行 -
scheduler
:一个调度函数,如果存在调度函数,在触发依赖时,执行该调度函数 -
scope
:一个EffectScope
作用域对象 -
allowRecurse
:boolean
,允许递归 -
onStop
:effect
被停止时的钩子
在effect
中会首先检查fn.effect
属性,如果存在fn.effect
,那么说明fn
已经被effect
处理过了,然后使用fn.effect.fn
作为fn
。
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const fn = () => {}
const runner1 = effect(fn)
const runner2 = effect(runner1)
runner1.effect.fn === fn // true
runner2.effect.fn === fn // true
然后new
了一个ReactiveEffect
对象。
const _effect = new ReactiveEffect(fn)
接着如果存在option
对象的话,会将options
,合并到_effect
中。如果存在options.scope
,会调用recordEffectScope
将_effect
放入options.scope
。如果不存在options
或options.lazy === false
,那么会执行_effect.run()
,进行依赖的收集。
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
最后,会将_effect.run
中的this
指向它本身,这样做的目的是用户在主动执行runner
时,this
指针指向的是_effect
对象,然后将_effect
作为runner
的effect
属性,并将runner
返回。
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
在effect
中创建了一个ReactiveEffect
对象,这个ReactiveEffect
是什么呢?接下来继续看ReactiveEffect
的实现。
ReactiveEffect
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
computed?: ComputedRefImpl<T>
allowRecurse?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
ReactiveEffect
是使用es6 class
定义的一个类。它的构造器可以接受三个参数:fn
(副作用函数)、scheduler
(调度器)、scope
(一个EffectScope
作用域对象),在构造器中调用了一个recordEffectScope
方法,这个方法会将当前ReactiveEffect
对象(this
)放入对应的EffectScope
作用域(scope
)中。
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
ReactiveEffect
中有两个方法:run
、stop
。
run
在run
的执行过程中,会首先判断ReactiveEffect
的激活状态(active
),如果未激活(this.active === false
),那么会立马执行this.fn
并返回他的执行结果。
if (!this.active) {
return this.fn()
}
然后声明了两个变量:parent
(默认activeEffect
)、lastShouldTrack
(默认shouldTrack
,一个全局变量,默认为true
)。紧接着会使用while
循环寻找parent.parent
,一旦parent
与this
相等,立即结束循环。
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
紧接着把activeEffect
赋值给this.parent
,把this
赋值给this.parent
。
try {
// 设置当前的parent为上一个activeEffect
this.parent = activeEffect
// 设置activeEffect为当前ReactiveEffect实例,activeEffect是个全局变量
activeEffect = this
shouldTrack = true
// ...
}
// ...
这样做的目的是,建立一个嵌套effect
的关系,来看下面一个例子:
const obj = reactive({a: 1})
effect(() => {
console.log(obj.a)
effect(() => {
console.log(obj.a)
})
})
当执行第一层_effect.run
时,因为默认的activeEffect
为undefined
,所以第一层effect
中的_effect.parent=undefined
,紧接着把this
赋值给activeEffect
,这时activeEffect
指向的第一层的_effect
。
在第一层中的_effect.run
执行过程中,最后会执行this.fn()
,在执行this.fn()
的过程中,会创建第二层effect
的ReactiveEffect
对象,然后执行_effect.run
,因为在第一层中_effect.run
运行过程中,已经将第一层的_effect
赋给了activeEffect
,所以第二层中的_effect.parent
指向了第一层的_effect
,紧接着又将第二次的_effect
赋给了activeEffect
。这样以来第一层effect
与第二层effect
就建立了联系。
当与父effect
建立联系后,有这么一行代码:
trackOpBit = 1 << ++effectTrackDepth
其中effectTrackDepth
是个全局变量为effect
的深度,层数从1开始计数,trackOpBit
使用二进制标记依赖收集的状态(如00000000000000000000000000000010
表示所处深度为1)。
紧接着会进行一个条件的判断:如果effectTrackDepth
未超出最大标记位(maxMarkerBits = 30
),会调用initDepMarkers
方法将this.deps
中的所有dep
标记为已经被track
的状态;否则使用cleanupEffect
移除deps
中的所有dep
。
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
这里为什么要标记已经被track
的状态或直接移除所有dep
?我们来看下面一个例子:
const obj = reactive({ str: 'objStr', flag: true })
effect(() => {
const c = obj.flag ? obj.str : 'no found'
console.log(c)
})
obj.flag = false
obj.str = 'test'
在首次track
时,targetMap
结构如下(targetMap
在下文中有介绍):
这时targetMap[toRaw(obj)]
(这里targetMap
的键是obj
的原始对象)中分别保存着str
、flag
共两份依赖。当执行obj.flag=false
后,会触发flag
对应的依赖,此时打印not found
。
当obj.flag
变为false
之后,副作用函数就不会受obj.str
的影响了,之后的操作,无论obj.str
如何变化,都不应该影响到副作用函数。这里标记dep
为已被track
或移除dep
的作用就是实现这种效果。由于obj.flag
的修改,会触发flag
对应的副作用函数(执行run
函数),此时this.deps
中保存着str
与flag
的对应的两份依赖,所以调用initDepMarkers
后,会将这两份依赖标记为已收集,当this.fn()
执行完毕后,会根据dep
某些属性,将str
所对应的依赖移除。这样无论修改str
为和值,都没有对应的依赖触发。
所以initDepMarkers(在finally移除)/cleanupEffect
的作用是移除多余的依赖。
回到run
函数中,最后需要执行this.fn()
,并将结果返回。这样就可以进行依赖的收集。在return fn()
之后继续进入finally
,在finally
中需要恢复一些状态:finalizeDepMarkers
根据一些状态移除多余的依赖、将effectTrackDepth
回退一层,activeEffect
指向当前ReactiveEffect
的parent
、shouldTrack = lastShouldTrack
、this.parent
置为undefined
try {
// ...
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
run
函数的作用就是会调用fn
,并返回其结果,在执行fn
的过程中会命中响应式对象的某些拦截操作,在拦截过程中进行依赖的收集。
stop
当调用stop
函数后,会调用cleanupEffect
将ReactiveEffect
中所有的依赖删除,然后执行onStop
钩子,最后将this.active
置为false
。
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
依赖收集
通过上面对effect
的分析,在effect
中如果未设置options.lazy = false
的话,会直接执行_effect.run()
,而在run()
方法中最后最终会调用副作用函数fn
。在fn
的执行过程中,会读取某个响应式数据,而我们的响应式数据是被Proxy
代理过的,一旦读取响应式数据的某个属性,就会触发Proxy
的get
操作(不一定是get
,这里以get
为例进行说明)。在拦截过程中会触发一个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)
}
}
track
函数接收三个参数:target
(响应式对象的原始对象)、type
(触发依赖操作的方式,有三种取值:TrackOpTypes.GET
、TrackOpTypes.HAS
、TrackOpTypes.ITERATE
)、key
(触发依赖收集的key
)。
track
中一上来就对shouldTrack
和activeEffect
进行了判断,只有shouldTrack
为true
且存在activeEffect
时才可以进行依赖收集。
如果可以进行依赖收集的话,会从targetMap
中获取target
对应的值,这里targetMap
保存着所有响应式数据所对应的副作用函数,它是个WeakMap
类型的全局变量,WeakMap
的键是响应式数据的原始对象target
,值是个Map
,而Map
的键是原始对象的key
,Map
的值时一个由副作用函数(一个ReactiveEffect
实例)组成的Set
集合。
为什么target
要使用WeakMap
,而不是Map
?因为WeakMap
的键是弱引用,如果target
被销毁后,那么它对应的值Map
也会被回收。如果你不了解WeakMap
的使用,请参考:MDN
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
targetMap.png
如果从targetMap
找不到target
对应的值,则创建一个Map
对象,存入targetMap
中。
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
然后从depsMap
中获取key
对应的副作用集合,如果不存在,则创建一个Set
,存入depsMap
中。这里创建Set
的过程中,会为Set
实例添加两个属性:n
、w
。w
表示在副作用函数执行前dep
是否已经被收集过了,n
表示在当前收集(本次run
执行)过程中dep
是新收集的。
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
最后调用trackEffects
方法。
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
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 {
// 直接判断dep中是否含有activeEffect
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
Object.assign(
{
effect: activeEffect!
},
debuggerEventExtraInfo
)
)
}
}
}
trackEffects
接收两个参数:dep
(ReactiveEffect
集合),debuggerEventExtraInfo
(开发环境下activeEffect.onTrack
钩子所需的参数)。
在trackEffects
中说先声明了一个默认值为false
的shouldTrack
变量,它代表我们需不需要收集activeEffect
。
如果shouldTrack
为true
的话,则将activeEffect
添加到dep
中,同时将dep
放入activeEffect.deps
中。
shouldTrack
的确定和dep
的n
、w
属性密切相关。如果newTracked(dep) === true
,说明在本次run
方法执行过程中,dep
已经被收集过了,shouldTrack
不变;如果newTracked(dep) === false
,要把dep
标记为新收集的,虽然dep
在本次收集过程中是新收集的,但它可能在之前的收集过程中已经被收集了,所以shouldTrack
的值取决于dep
是否在之前已经被收集过了。
// wasTracked(dep)返回true,意味着dep在之前的依赖收集过程中已经被收集过,或者说在之前run执行过程中已经被收集
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
// newTracked(dep)返回true,意味着dep是在本次依赖收集过程中新收集到的,或者说在本次run执行过程中新收集到的
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
这里使用以下例子来说明shouldTrack
的确认过程:
let sum
const counter = reactive({ num1: 0, num2: 0 })
effect(() => {
sum = counter.num1 + counter.num1 + counter.num2
})
在上面例子中共经历3次依赖收集的过程。
-
第一次因为访问到
track-flow1.pngcounter.num1
,被counter
的get
拦截器拦截,因为最开始targetMap
是空的,所以在第一次收集过程中会进行初始化,此时targetMap[toRaw(counter)].num1.n/w=0
,当决定shouldTrack
的值时,因为newTracked(dep)===false
,所以shouldTrack=!wasTracked
,显然wasTracked(dep)===false
,shouldTrack
值被确定为true
,意味着依赖应该被收集,track
执行完成后的targetMap
结构为
-
第二次同样访问到
counter.num1
,被counter
的get
拦截器拦截,并开始收集依赖,但在这次收集过程中,因为newTracked(dep) === true
,所以shouldTrack
为false
,本次不会进行依赖的收集 -
第三次访问到
track-flow2.pngcounter.num2
,过程与第一次相同,当本次track
执行完毕后,targetMap
结构为
-
3次依赖收集完毕,意味着
fn
执行完毕,进入finally
中,执行finalizeDepMarkers
,此时会将_effect.deps
中的dep.n
恢复至0
触发依赖
在依赖被收集完成后,一旦响应式数据的某些属性改变后,就会触发对应的依赖。这个触发的过程发生在proxy
的set
、deleteProperty
拦截器、,或集合的get
拦截器(拦截clear
、add
、set
等操作)。
在触发依赖时,会执行一个trigger
函数:
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 获取target对相应的所有依赖,一个map对象
const depsMap = targetMap.get(target)
// 如果没有,说明没有依赖,直接return
if (!depsMap) {
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))
}
// 获取一些迭代的依赖,如map.keys、map.values、map.entries等
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))
}
}
}
trigger
可接收六个参数:
-
target
:响应式数据的原始对象 -
type
:操作类型。是个枚举类TriggerOpTypes
,共有四种操作类型:-
TriggerOpTypes.SET
:如obj.xx = xx
(修改属性)、map.set(xx, xx)
(修改操作不是新增操作)、arr[index] = xx
(index < arr.length
)、arr.length = 0
-
TriggerOpTypes.ADD
:如obj.xx = xx
(新增属性)、set.add(xx)
、map.set(xx, xx)
(新增操作)、arr[index] = xx
(index >= arr.length
) -
TriggerOpTypes.DELETE
:如delete obj.xx
、set/map.delete(xx)
-
TriggerOpTypes.CLEAR
:如map/set.clear()
-
-
key
:可选,触发trigger
的键,如obj.foo = 1
,key
为foo
。 -
newValue
:可选,新的值,如obj.foo = 1
,newValue
为1
。 -
oldValue
:可选,旧的值,如obj.foo = 1
,oldValue
为修改前的obj.foo
。 -
oldTarget
:可选,旧的原始对象,只在开发模式下有用。
在trigger
中首先要获取target
对应的所有依赖depsMap
,如果没有的直接return
。
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
接下来需要根据key
与type
获取触发的依赖(使用deps
存放需要触发的依赖),这里分为如下几个分支:
-
type === TriggerOpTypes.CLEAR
:意味着调用了map/set.clear()
,map/set
被清空,这时与map/set
相关的所有依赖都需要被触发。
deps = [...depsMap.values()]
-
key === 'length' && isArray(target)
:当操作的的是array
的length
属性,如arr.length = 1
,这时要获取的依赖包括:length
属性的依赖以及索引大于等于新的length
的依赖。
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
- 其他情况:
- 首先从
depsMap
中获取对应key
的依赖,depsMap.get(key)
。
if (key !== void 0) { // void 0 等价于undefined deps.push(depsMap.get(key)) }
- 然后再找一些迭代的依赖,如
keys、values、entries
操作。 -
TriggerOpTypes.ADD
:如果不是数组,获取ITERATE_KEY
的依赖,如果是Map
获取MAP_KEY_ITERATE_KEY
的依赖;如果是数组并且key
是索引,获取length
对应的依赖
if (!isArray(target)) { // target不是数组 deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // key是整数,获取length对应的依赖 deps.push(depsMap.get('length')) }
-
TriggerOpTypes.DELETE
:如果不是数组,获取ITERATE_KEY
的依赖,如果是Map
获取MAP_KEY_ITERATE_KEY
的依赖
if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } }
-
TriggerOpTypes.SET
:如果是Map
,获取ITERATE_KEY
的依赖
if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) }
- 首先从
这里简单介绍下ITERATE_KEY
和MAP_KEY_ITERATE_KEY
存储的依赖是由什么操作引起的
ITERATE_KEY
中的依赖是由这些操作触发进行收集:获取集合的size
、集合的forEach
操作、集合的迭代操作(包括keys
(非Map)、values
、entries
、Symbol.iterator
(for...of
))
MAP_KEY_ITERATE_KEY
中的依赖的由map.keys()
触发进行收集。
对应上面其他情况中的几个分支:
- 如果对响应式数据的改动是一种新增操作的话,受影响的操作有:集合的
size
、集合的forEach
、集合的迭代操作。 - 如果改动是删除操作,受影响的操作有:集合的
size
、集合的forEach
、集合的迭代操作。 - 如果改动是修改操作,因为只有
map.set()
可以实现修改集合的操作,所以受影响的操作只有Map
的迭代操作和forEach
当收集完需要触发的依赖,下一步就是要触发依赖:
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))
}
}
这里有两个分支:
- 如果
deps.length
为1,且存在des[0]
,则调用triggerEffects(deps[0])
- 否则将遍历
deps
并解构,将每一个effect
放入一个effects
中,然后在调用triggerEffects
时,利用Set
去重:triggerEffects(createDep(effects))
triggerEffects
函数可以接收两个参数:dep
一个数组或Set集合,保存着需要触发的依赖、debuggerEventExtraInfo
在开发环境下,effect.onTrigger
所需的一些信息。
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
在triggerEffects
中,会遍历dep
,如果dep
中的effect
不是当前活跃的effect
(activeEffect
)或effect.allowRecurse
为true
,则会根据是否有effect.scheduler
,执行effect.scheduler
或effect.run
。 至此,依赖触发过程结束。
接下来详细看下在this.fn()
执行完毕后,多余的依赖是如何根据n
、w
属性移除的(此处值只考虑深度在31层以内的,超出31(包含31)层会直接调用cleanupEffect
方法删除,比较简单,此处不进行详细说明):
我们还是以前面的例子来分析:
const obj = reactive({ str: 'objStr', flag: true })
effect(() => {
const c = obj.flag ? obj.str : 'no found'
console.log(c)
})
obj.flag = false
obj.str = 'test'
-
effect
执行过程中,创建ReactiveEffect
实例,这里以_effect
表示,因为未指定lazy
,所以会执行_effect.run()
- 执行
this.fn()
,在fn
执行过程中会访问到obj
的flag
和str
属性,从而被obj
的get
拦截器进行拦截,在拦截过程中会调用track
进行依赖的收集,this.fn()
执行完毕后targetMap
结构如下
effect-flow1.png - 然后进入
finally
,执行finalizeDepMarkers
,因为wasTracked(dep)
为false
,所以不会删除依赖,但会执行dep.n &= ~trackOpBit
,清除比特位。最终targetMap
结构为:
effect-flow2.png - 当执行
obj.flag = false
时,会触发flag
属性对应的依赖,执行trigger
,在trigger
中获取flag
对应的依赖set1
,然后调用triggerEffects
,在triggerEffects
中,执行_effect.run
。 - 在这次
run
执行过程中,会将_effect.deps
中的依赖集合都标记为已收集状态:
effect-flow3.png - 然后执行
this.fn()
,同样执行fn
的过程中,被obj
的get
拦截器拦截,不过这次只拦截了flag
属性。在trackEffects
中检测到newTracked(dep) === false
(此处dep
就是set1
),所以执行dep.n |= trackOpBit
操作,将set1
标记为本轮收集过程中新的依赖,又因为wasTracked(dep) === true
,所以shouldTrack
为false
,本次不会收集依赖。至此,targetMap
结构为:
effect-flow4.png - 当
this.fn()
执行完毕,进入finally
,执行finalizeDepMarkers
。在finalizeDepMarkers
中会遍历effect.deps
,根据n
、w
属性移除依赖。 - 首先判断
set1
,因为wasTracked(dep) === true
、newTracked(dep) === true
,所以执行deps[ptr++] = dep
,将set1
放在deps
索引为0的位置,同时ptr
自增1,然后执行dep.w &= ~trackOpBit
、dep.n &= ~trackOpBit
。最终set1.n/w = 0
- 接着判断
set2
,因为wasTracked(dep) === true
、newTracked(dep) === false
,所以执行dep.delete(effect)
,将_effect
从set2
中删除,然后执行dep.w &= ~trackOpBit
、dep.n &= ~trackOpBit
。最终set2.n/w = 0
,set2
中无依赖。 - 遍历完毕,执行
deps.length = ptr
(ptr
此时为1)。也就是说把set2
从deps
中移除了。 -
finally
执行完毕后,targetMap
结构为:
effect-flow5.png
可以看到str
对应的依赖已经没有了。 - 当执行
obj.str = 'test'
时,触发trigger
函数,但此时在targetMap
中已经没有str
对应的依赖了,所以在trigger
中直接return
,结束。
网友评论