关于前端:vue3源码二vue3的响应系统分析

25次阅读

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

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

正文完
 0