前言

参考代码版本:vue 3.2.37

官网文档:https://vuejs.org/

对于为什么要有effectScope能够参考RFC

应用示例

effectScope能够对外部的响应式对象的副作用effect进行对立治理。

const counter = ref(1)const scope = effectScope()scope.run(() => {  const doubled = computed(() => counter.value * 2)  watch(doubled, () => console.log(doubled.value))  watchEffect(() => console.log('Count: ', doubled.value))})// 解决掉以后作用域内的所有 effectscope.stop()

effectScope接管一个boolean值,如果传true代表游离模式,那么创立的scope不会被父scope收集,艰深来讲,如果是游离模式,那么scope之间是不存在父子关系的,每一个scope都是独立的。

export function effectScope(detached?: boolean) {  return new EffectScope(detached)}

effectScope返回一个EffectScope实例。

EffectScope

export class EffectScope {  active = true  effects: ReactiveEffect[] = []  cleanups: (() => void)[] = []  parent: EffectScope | undefined  scopes: EffectScope[] | undefined  /**   * track a child scope's index in its parent's scopes array for optimized   * removal   */  private index: number | undefined  constructor(detached = false) {    if (!detached && activeEffectScope) {      this.parent = activeEffectScope      this.index =        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(          this        ) - 1    }  }  run<T>(fn: () => T): T | undefined {    if (this.active) {      try {        activeEffectScope = this        return fn()      } finally {        activeEffectScope = this.parent      }    } else if (__DEV__) {      warn(`cannot run an inactive effect scope.`)    }  }  on() {    activeEffectScope = this  }  off() {    activeEffectScope = this.parent  }  stop(fromParent?: boolean) {    if (this.active) {      let i, l      for (i = 0, l = this.effects.length; i < l; i++) {        this.effects[i].stop()      }      for (i = 0, l = this.cleanups.length; i < l; i++) {        this.cleanups[i]()      }      if (this.scopes) {        for (i = 0, l = this.scopes.length; i < l; i++) {          this.scopes[i].stop(true)        }      }      // nested scope, dereference from parent to avoid memory leaks      if (this.parent && !fromParent) {        // optimized O(1) removal        const last = this.parent.scopes!.pop()        if (last && last !== this) {          this.parent.scopes![this.index!] = last          last.index = this.index!        }      }      this.active = false    }  }}

constructor

EffectScope结构器接管一个参数:detached,默认值为false,代表EffectScope是否是游离状态。

constructor(detached = false) {  if (!detached && activeEffectScope) {    this.parent = activeEffectScope    this.index =      (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(        this      ) - 1  }}

如果detachedfalse,并且存在activeEffectScopeactiveEffectScope是个全局变量)的状况,会将activeEffectScope赋值给this.parent,同时会将以后EffectScope实例放入activeEffectScope.scopes中,并将activeEffectScope.scopes最初一个索引赋值给以后EffectScope实例的index属性。这样就能够通过this.index来获取EffectScope实例在父scope中的索引地位。

run

run办法能够接管一个函数参数。

run<T>(fn: () => T): T | undefined {  if (this.active) {    try {      activeEffectScope = this      return fn()    } finally {      activeEffectScope = this.parent    }  } else if (__DEV__) {    warn(`cannot run an inactive effect scope.`)  }}

run办法会首先对this.active进行判断,如果this.activetrue,也就是EffectScope处于激活状态,那么会将this赋给activeEffectScope,而后执行fn,并返回其执行后果。当fn执行结束后,将activeEffectScope改为this.parent

on

on() {  activeEffectScope = this}

on办法会将activeEffectScope指向以后EffectScope实例。

off

off() {  activeEffectScope = this.parent}

off办法会将activeEffectScope指向以后EffectScope实例的父scope

stop

stop函数的作用是革除scope内的所有的响应式成果,包含子scopestop接管一个boolean类型的fromParent参数,如果fromParenttruestop将不会删除在父scope中的援用。

stop(fromParent?: boolean) {  if (this.active) {    let i, l    // 调用ReactiveEffect.prototype.stop,革除scope内所有响应式成果    for (i = 0, l = this.effects.length; i < l; i++) {      this.effects[i].stop()    }    // 触发scope销毁时的监听函数    for (i = 0, l = this.cleanups.length; i < l; i++) {      this.cleanups[i]()    }    // 销毁子scope    if (this.scopes) {      for (i = 0, l = this.scopes.length; i < l; i++) {        this.scopes[i].stop(true)      }    }    // 嵌套范畴,从父级勾销援用以防止内存透露    if (this.parent && !fromParent) {      // 获取父scope的中最初一个scope      const last = this.parent.scopes!.pop()      // last不是以后的scope      if (last && last !== this) {        // 将last放在以后scope在parent.scopes中的索引地位        this.parent.scopes![this.index!] = last        // last.index改为this.index        last.index = this.index!      }    }    // 批改scope的激活状态    this.active = false  }}

stop中的所有操作都要建设在scope处于激活状态的根底上。首先遍历this.effects执行元素的stop办法。

for (i = 0, l = this.effects.length; i < l; i++) {  this.effects[i].stop()}

scope.effects存储的是在run过程中获取到的ReactiveEffect实例,这些ReactiveEffect实例会通过一个recordEffectScope办法被增加到scope.effects中。

export function recordEffectScope(  effect: ReactiveEffect,  scope: EffectScope | undefined = activeEffectScope) {  if (scope && scope.active) {    scope.effects.push(effect)  }}

当遍历完scope.effects或,会遍历scope.cleanups属性。

for (i = 0, l = this.cleanups.length; i < l; i++) {      this.cleanups[i]()    }

scope.cleanups中保留的是通过onScopeDispose增加的scope销毁监听函数。

export function onScopeDispose(fn: () => void) {  if (activeEffectScope) {    activeEffectScope.cleanups.push(fn)  } else if (__DEV__) {    warn(      `onScopeDispose() is called when there is no active effect scope` +        ` to be associated with.`    )  }}

如果以后scope存在scopes属性,意味着以后scope存在子scope,所以须要将所有子scope也进行销毁。

if (this.scopes) {  for (i = 0, l = this.scopes.length; i < l; i++) {    this.scopes[i].stop(true)  }}

如果以后scope存在parent的话,须要将scope从其parent中移除。

if (this.parent && !fromParent) {  // 获取父scope的中最初一个scope  const last = this.parent.scopes!.pop()  // last不是以后的scope  if (last && last !== this) {    // 将last放在以后scope在parent.scopes中的索引地位    this.parent.scopes![this.index!] = last    // last.index改为this.index    last.index = this.index!  }}

这里的移除过逻辑是,先获取以后scope的父scope中的所有子scope,而后取出最初一个scope,这里用last代表(留神last不肯定和以后scope雷同),如果last和以后scope不同的话,须要让last替换以后scope,这样咱们就把以后scope从其父scope中移除了。这里仅仅替换是不够的,因为last.index此时还是之前父scope的最初一个索引,所以还须要把last.index改为以后scope在其父scope.scopes中的地位。这样就齐全移除了scope

最初,须要把scope的激活状态改为false

this.active = false

getCurrentScope

getCurrentScope能够获取以后处于沉闷状态的EffectScope。这里处于沉闷状态的EffectScope指得是以后执行环境在所处的那个EffectScope

export function getCurrentScope() {  return activeEffectScope}