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

  • 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 // true
runner2.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 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中有两个办法: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 = 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时,因为默认的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 = false

obj.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 }
  : 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接管两个参数: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 sum
const 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 等价于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_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 = false

obj.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,完结。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理