vue3 effectScope源码解析

本文详细解析了Vue中的EffectScope类,包括它的构造函数、run、on、off和stop方法,阐述了如何管理和停止响应式效果,以及在作用域之间的关系和清理过程。重点介绍了stop方法中对子scope的销毁以及从父scope中移除的逻辑。
摘要由CSDN通过智能技术生成
let activeEffectScope;
// effectScope可以对内部的响应式对象的副作用effect进行统一管理。
class EffectScope {
    //effectScope接收一个boolean值,如果传true代表游离模式,那么创建的scope不会被父scope收集,通俗来讲,如果是游离模式,那么scope之间是不存在父子关系的,每一个scope都是独立的。
    constructor(detached = false) {
        /**
         * @internal //内部的
         */
        this.active = true;
        /**
         * @internal
         */
        this.effects = [];
        /**
         * @internal
         */
        this.cleanups = [];
        if (!detached && activeEffectScope) {
            this.parent = activeEffectScope;
            this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1;
        }
    }
    //如果detached为false,并且存在activeEffectScope(activeEffectScope是个全局变量)的情况,会将activeEffectScope赋值给this.parent,
    //同时会将当前EffectScope实例放入activeEffectScope.scopes中,并将activeEffectScope.scopes最后一个索引赋值给当前EffectScope实例的index属性。
    //这样就可以通过this.index来获取EffectScope实例在父scope中的索引位置。


    //run方法会首先对this.active进行判断,如果this.active为true,也就是EffectScope处于激活状态,那么会将this赋给activeEffectScope,然后执行fn,并返回其执行结果。
    //当fn执行完毕后,将activeEffectScope改为this.parent。
    run(fn) {
        if (this.active) { 
            const currentEffectScope = activeEffectScope;
            try {
                activeEffectScope = this;
                return fn();
            }
            finally {
                activeEffectScope = currentEffectScope;
            }
        }
        else {
            warn(`cannot run an inactive effect scope.`);
        }
    }
    /**
     * This should only be called on non-detached scopes
     * 不分离 -- non-detached
     * @internal
     */
    // on方法会将activeEffectScope指向当前EffectScope实例。
    on() {
        activeEffectScope = this;
    }
    /**
     * This should only be called on non-detached scopes
     * @internal
     */
    //off方法会将activeEffectScope指向当前EffectScope实例的父scope。
    off() {
        activeEffectScope = this.parent;
    }

    //stop函数的作用是清除scope内的所有的响应式效果,包括子scope。
    //stop接收一个boolean类型的fromParent参数,如果fromParent为true,stop将不会删除在父scope中的引用。
    stop(fromParent) {
        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);
                }
            }
            // 嵌套范围,从父级取消引用以避免内存泄漏
            // nested scope, dereference from parent to avoid memory leaks
            if (this.parent && !fromParent) {
                // optimized O(1) removal  优化的O(1)去除
                // 获取父scope的中最后一个scope
                const last = this.parent.scopes.pop();
                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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值