共计 15897 个字符,预计需要花费 40 分钟才能阅读完成。
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
找不到 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 次依赖收集的过程。
- 第一次因为拜访到
counter.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
,本次不会进行依赖的收集 - 第三次拜访到
counter.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
构造如下 - 而后进入
finally
,执行finalizeDepMarkers
,因为wasTracked(dep)
为false
,所以不会删除依赖,但会执行dep.n &= ~trackOpBit
,革除比特位。最终targetMap
构造为: - 当执行
obj.flag = false
时,会触发flag
属性对应的依赖,执行trigger
,在trigger
中获取flag
对应的依赖set1
,而后调用triggerEffects
,在triggerEffects
中,执行_effect.run
。 - 在这次
run
执行过程中,会将_effect.deps
中的依赖汇合都标记为已收集状态: - 而后执行
this.fn()
,同样执行fn
的过程中,被obj
的get
拦截器拦挡,不过这次只拦挡了flag
属性。在trackEffects
中检测到newTracked(dep) === false
(此处dep
就是set1
),所以执行dep.n |= trackOpBit
操作,将set1
标记为本轮收集过程中新的依赖,又因为wasTracked(dep) === true
,所以shouldTrack
为false
,本次不会收集依赖。至此,targetMap
构造为: - 当
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
构造为:
能够看到 str
对应的依赖曾经没有了。
- 当执行
obj.str = 'test'
时,触发trigger
函数,但此时在targetMap
中曾经没有str
对应的依赖了,所以在trigger
中间接return
,完结。