【vue3源码】二、vue3的响应系统分析
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 = 0TriggerOpTypes.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,结束。
转载自:https://segmentfault.com/a/1190000042049605