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 // truerunner2.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 ReactiveEffectRunnerrunner.effect = _effectreturn 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 = activeEffectlet lastShouldTrack = shouldTrackwhile (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 = falseobj.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 } : undefinedtrackEffects(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 sumconst 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 等价于undefineddeps.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 = falseobj.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
,完结。