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,第二个参数是个对象,该对象能够有如下属性:

  • lazyboolean,是否懒加载,如果是true,调用effect不会立刻执行监听函数,须要用户手动执行
  • scheduler:一个调度函数,如果存在调度函数,在触发依赖时,执行该调度函数
  • scope:一个EffectScope作用域对象
  • allowRecurseboolean,容许递归
  • onStopeffect被进行时的钩子

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。如果不存在optionsoptions.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作为runnereffect属性,并将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中有两个办法:runstop

run

run的执行过程中,会首先判断ReactiveEffect的激活状态(active),如果未激活(this.active === false),那么会立马执行this.fn并返回他的执行后果。

if (!this.active) {  return this.fn()}

而后申明了两个变量:parent(默认activeEffect)、lastShouldTrack(默认shouldTrack,一个全局变量,默认为true)。紧接着会应用while循环寻找parent.parent,一旦parentthis相等,立刻完结循环。

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时,因为默认的activeEffectundefined,所以第一层effect中的_effect.parent=undefined,紧接着把this赋值给activeEffect,这时activeEffect指向的第一层的_effect

在第一层中的_effect.run执行过程中,最初会执行this.fn(),在执行this.fn()的过程中,会创立第二层effectReactiveEffect对象,而后执行_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的原始对象)中别离保留着strflag共两份依赖。当执行obj.flag=false后,会触发flag对应的依赖,此时打印not found

obj.flag变为false之后,副作用函数就不会受obj.str的影响了,之后的操作,无论obj.str如何变动,都不应该影响到副作用函数。这里标记dep为已被track或移除dep的作用就是实现这种成果。因为obj.flag的批改,会触发flag对应的副作用函数(执行run函数),此时this.deps中保留着strflag的对应的两份依赖,所以调用initDepMarkers后,会将这两份依赖标记为已收集,当this.fn()执行结束后,会依据dep某些属性,将str所对应的依赖移除。这样无论批改str为和值,都没有对应的依赖触发。

所以initDepMarkers(在finally移除)/cleanupEffect的作用是移除多余的依赖。

回到run函数中,最初须要执行this.fn(),并将后果返回。这样就能够进行依赖的收集。在return fn()之后持续进入finally,在finally中须要复原一些状态:finalizeDepMarkers依据一些状态移除多余的依赖、将effectTrackDepth回退一层,activeEffect指向以后ReactiveEffectparentshouldTrack = lastShouldTrackthis.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函数后,会调用cleanupEffectReactiveEffect中所有的依赖删除,而后执行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代理过的,一旦读取响应式数据的某个属性,就会触发Proxyget操作(不肯定是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.GETTrackOpTypes.HASTrackOpTypes.ITERATE)、key(触发依赖收集的key)。

track中一上来就对shouldTrackactiveEffect进行了判断,只有shouldTracktrue且存在activeEffect时才能够进行依赖收集。

如果能够进行依赖收集的话,会从targetMap中获取target对应的值,这里targetMap保留着所有响应式数据所对应的副作用函数,它是个WeakMap类型的全局变量,WeakMap的键是响应式数据的原始对象target,值是个Map,而Map的键是原始对象的keyMap的值时一个由副作用函数(一个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实例增加两个属性:nww示意在副作用函数执行前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接管两个参数:depReactiveEffect汇合),debuggerEventExtraInfo(开发环境下activeEffect.onTrack钩子所需的参数)。

trackEffects中说先申明了一个默认值为falseshouldTrack变量,它代表咱们需不需要收集activeEffect

如果shouldTracktrue的话,则将activeEffect增加到dep中,同时将dep放入activeEffect.deps中。

shouldTrack的确定和depnw属性密切相关。如果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次依赖收集的过程。

  1. 第一次因为拜访到counter.num1,被counterget拦截器拦挡,因为最开始targetMap是空的,所以在第一次收集过程中会进行初始化,此时targetMap[toRaw(counter)].num1.n/w=0,当决定shouldTrack的值时,因为newTracked(dep)===false,所以shouldTrack=!wasTracked,显然wasTracked(dep)===falseshouldTrack值被确定为true,意味着依赖应该被收集,track执行实现后的targetMap构造为
  2. 第二次同样拜访到counter.num1,被counterget拦截器拦挡,并开始收集依赖,但在这次收集过程中,因为newTracked(dep) === true,所以shouldTrackfalse,本次不会进行依赖的收集
  3. 第三次拜访到counter.num2,过程与第一次雷同,当本次track执行结束后,targetMap构造为
  4. 3次依赖收集结束,意味着fn执行结束,进入finally中,执行finalizeDepMarkers,此时会将_effect.deps中的dep.n复原至0

触发依赖

在依赖被收集实现后,一旦响应式数据的某些属性扭转后,就会触发对应的依赖。这个触发的过程产生在proxysetdeleteProperty拦截器、,或汇合的get拦截器(拦挡clearaddset等操作)。

在触发依赖时,会执行一个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.xxset/map.delete(xx)
    • TriggerOpTypes.CLEAR:如map/set.clear()
  • key:可选,触发trigger的键,如obj.foo = 1keyfoo
  • newValue:可选,新的值,如obj.foo = 1newValue1
  • oldValue:可选,旧的值,如obj.foo = 1oldValue为批改前的obj.foo
  • oldTarget:可选,旧的原始对象,只在开发模式下有用。

trigger中首先要获取target对应的所有依赖depsMap,如果没有的间接return

const depsMap = targetMap.get(target)if (!depsMap) { return}

接下来须要依据keytype获取触发的依赖(应用deps寄存须要触发的依赖),这里分为如下几个分支:

  • type === TriggerOpTypes.CLEAR:意味着调用了map/set.clear()map/set被清空,这时与map/set相干的所有依赖都须要被触发。

    deps = [...depsMap.values()]
  • key === 'length' && isArray(target):当操作的的是arraylength属性,如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_KEYMAP_KEY_ITERATE_KEY存储的依赖是由什么操作引起的

ITERATE_KEY中的依赖是由这些操作触发进行收集:获取汇合的size、汇合的forEach操作、汇合的迭代操作(包含keys(非Map)、valuesentriesSymbol.iteratorfor...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不是以后沉闷的effectactiveEffect)或effect.allowRecursetrue,则会依据是否有effect.scheduler,执行effect.schedulereffect.run。 至此,依赖触发过程完结。

接下来具体看下在this.fn()执行结束后,多余的依赖是如何依据nw属性移除的(此处值只思考深度在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'
  1. effect执行过程中,创立ReactiveEffect实例,这里以_effect示意,因为未指定lazy,所以会执行_effect.run()
  2. 执行this.fn(),在fn执行过程中会拜访到objflagstr属性,从而被objget拦截器进行拦挡,在拦挡过程中会调用track进行依赖的收集,this.fn()执行结束后targetMap构造如下
  3. 而后进入finally,执行finalizeDepMarkers,因为wasTracked(dep)false,所以不会删除依赖,但会执行dep.n &= ~trackOpBit,革除比特位。最终targetMap构造为:
  4. 当执行obj.flag = false时,会触发flag属性对应的依赖,执行trigger,在trigger中获取flag对应的依赖set1,而后调用triggerEffects,在triggerEffects中,执行_effect.run
  5. 在这次run执行过程中,会将_effect.deps中的依赖汇合都标记为已收集状态:
  6. 而后执行this.fn(),同样执行fn的过程中,被objget拦截器拦挡,不过这次只拦挡了flag属性。在trackEffects中检测到newTracked(dep) === false(此处dep就是set1),所以执行dep.n |= trackOpBit操作,将set1标记为本轮收集过程中新的依赖,又因为wasTracked(dep) === true,所以shouldTrackfalse,本次不会收集依赖。至此,targetMap构造为:
  7. this.fn()执行结束,进入finally,执行finalizeDepMarkers。在finalizeDepMarkers中会遍历effect.deps,依据nw属性移除依赖。
  8. 首先判断set1,因为wasTracked(dep) === truenewTracked(dep) === true,所以执行deps[ptr++] = dep,将set1放在deps索引为0的地位,同时ptr自增1,而后执行dep.w &= ~trackOpBitdep.n &= ~trackOpBit。最终set1.n/w = 0
  9. 接着判断set2,因为wasTracked(dep) === truenewTracked(dep) === false,所以执行dep.delete(effect),将_effectset2中删除,而后执行dep.w &= ~trackOpBitdep.n &= ~trackOpBit。最终set2.n/w = 0set2中无依赖。
  10. 遍历结束,执行deps.length = ptrptr此时为1)。也就是说把set2deps中移除了。
  11. finally执行结束后,targetMap构造为:

能够看到str对应的依赖曾经没有了。

  1. 当执行obj.str = 'test'时,触发trigger函数,但此时在targetMap中曾经没有str对应的依赖了,所以在trigger中间接return,完结。