mobx(5.14.0)源码解读二 autorun

486 阅读4分钟

前言

上文中,已经介绍了observable是如何工作的,接下来我们来看看aurorun又是怎么运行的?

开始解读

一、如何执行autorun回调函数的第一次调用?

废话不多说,直接进入主题,我们先来看下,在程序中是如何调用autorun的?demo中的代码如下

autorun(() => {
  pointsLabel.innerText = `[${vipMember.name}] has [${vipMember.points}] points`
})

autorun方法传入一个参数,很明显是一个回调函数,Chrome调试断点进入或者直接找到node_modules/mobx/lib/mobx.module.js文件中的autorun方法定义

function autorun(view, opts) {
    if (opts === void 0) { opts = EMPTY_OBJECT; }
    ...
    var name = (opts && opts.name) || view.name || "Autorun@" + getNextId();
    var runSync = !opts.scheduler && !opts.delay;
    var reaction;
    if (runSync) {
        // Code-1
        reaction = new Reaction(name, function () {
            this.track(reactionRunner);
        }, opts.onError, opts.requiresObservable);
    } else {
        ...
    }
    // Code-2
    function reactionRunner() {
        view(reaction);
    }
    // Code-3
    reaction.schedule();
    return reaction.getDisposer();
}

(1)这时候我们传入的opts是undefined,为其设置一个默认值{},所以runSync这个时候为true,进入if逻辑。

(2)创建一个Reaction的实例化对象,参数中带有一个回调,去调用reaction.track方法,其中参数为下文定义的reactionRunner方法

(3)定义reactionRunner方法,去调用autorun回调函数

(4)可以看出来,以上的几步操作,都还不是autorun入口,程序不会往下执行,所以reaction.schedule方法才是开始。

接下来看看Reaction是一个什么样的Class?

Code-1

(1)拥有onInvalidate属性,其值等于function () { this.track(reactionRunner); }

(2)原型属性拥有schedule、track、runReaction等方法,这些方法在后面都会被调用

接下来我们以reaction.schedule为切入点,一步一步的分析autorun的运行原理

Code-3

Reaction.prototype.schedule = function () {
    if (!this._isScheduled) {
        this._isScheduled = true;
        globalState.pendingReactions.push(this);
        // Code-3-1
        runReactions();
    }
};

将reaction添加进globalState.pendingReactions,再执行runReactions方法。

Code-3-1

var reactionScheduler = function (f) { return f(); };
function runReactions() {
    // Trampolining, if runReactions are already running, new reactions will be picked up
    if (globalState.inBatch > 0 || globalState.isRunningReactions)
        return;
    reactionScheduler(runReactionsHelper);
}
function runReactionsHelper() {
    globalState.isRunningReactions = true;
    var allReactions = globalState.pendingReactions;
    var iterations = 0;
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            console.error("Reaction doesn't converge to a stable state after " + MAX_REACTION_ITERATIONS + " iterations." +
                (" Probably there is a cycle in the reactive function: " + allReactions[0]));
            allReactions.splice(0); // clear reactions
        }
        var remainingReactions = allReactions.splice(0);
        for (var i = 0, l = remainingReactions.length; i < l; i++)
            // Code-3-1-1
            remainingReactions[i].runReaction();
    }
    globalState.isRunningReactions = false;
}

(1)runReactions实际上就是调用runReactionsHelper方法。

(2)对Code-3中push的reactions进行循环遍历,并执行remainingReactions[i].runReaction();,即调用Reaction.prototype.runReaction。

Code-3-1-1

Reaction.prototype.runReaction = function () {
        if (!this.isDisposed) {
            startBatch();
            ...
            this.onInvalidate();
            ...
            endBatch();
        }
    };

可以看到,这里去调用了reaction.onInvalidate方法,即我们再new Reaction时传入的 function () { this.track(reactionRunner); }, 去调用Reaction.prototype.track方法,其中参数reactionRunner为autorun回调。

Code-3-1-1-1

Reaction.prototype.track = function (fn) {
    ...
    startBatch();
    ...
    var result = trackDerivedFunction(this, fn, undefined);
    ...
    endBatch();
}

这个方法,基本上都是一些if...else判断,直接跳过,找到非常关键的一句代码var result = trackDerivedFunction(this, fn, undefined);

Code-3-1-1-1-1

function trackDerivedFunction(derivation, f, context) {
    ...
    globalState.trackingDerivation = derivation;
    var result;
   if (globalState.disableErrorBoundaries === true) {
       result = f.call(context);
   }
   else {
       try {
           result = f.call(context);
       }
       catch (e) {
           result = new CaughtException(e);
       }
   }
   globalState.trackingDerivation = prevTracking;
   bindDependencies(derivation);
   ...
   return result;
}

(1)将derivation赋值给globalState.trackingDerivation。在介绍get拦截器逻辑中会使用到。

(2)无论是if还是else,都会执行result = f.call(context);,即执行autorun的回调,这时候就会触发Proxy的get拦截器,后续介绍。

(3)执行bindDependencies(derivation);最后是通过执行这步,将reaction挂载在$mobx.values[index].observers上的,稍候介绍。

二、执行autorun回调的第一次调用后,get拦截器又是如何运行的?

从以上的介绍中,我们可以看出,已经走到了autorun回调函数的逻辑,但是还没有真正的将observable和reaction关联起来,接下来我们再来看看get拦截器干了什么?

autorun(() => {
 pointsLabel.innerText = `[${vipMember.name}] has [${vipMember.points}] points`
})

demo中,我们读取vipMember的name和points属性,而这个时候,vipMember已经不是原来原始的object,而是一个Proxy实例,所以对proxy属性的读取,必定会触发拦截器,这时候我们去寻找Proxy的handler中的get方法,回到mobx(5.14.0)源码解读一 observable 介绍createDynamicObservableObject方法时提到的objectProxyTraps是如何定义get拦截器的?

var objectProxyTraps = {
    has: function (target, name) {},
    get: function (target, name) {
       if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
           return target[name];
       // (1)取出$mobx属性
       var adm = getAdm(target);
       // (2)根据属性key,读取ObservableValue实例
       var observable = adm.values.get(name);
       // (3)ObservableValue继承自Atom,所以为true
       if (observable instanceof Atom) {
           // (4)调用ObservableValue.prototype.get方法
           var result = observable.get();
           if (result === undefined) {
               adm.has(name);
           }
           // (5)返回
           return result;
       }
   },
   set: function (target, name, value) {}
}

(1)取出vipMember的$mobx属性。

(2)根据属性key(这里是name和points),读取ObservableValue实例

(3)ObservableValue继承自Atom,所以走if逻辑

为什么说ObservableValue继承自Atom,我们找到ObservableValue类的定义

var ObservableValue = /** @class */ (function (_super) {
   __extends(ObservableValue, _super);
   function ObservableValue(value, enhancer, name, notifySpy, equals) { ... }
   ObservableValue.prototype.dehanceValue = function(){}
}(Atom));
  

(4)调用ObservableValue.prototype.get方法

ObservableValue.prototype.get = function () {
    this.reportObserved();
    return this.dehanceValue(this.value);
};

this.reportObserved(); 报告:我被监视了!

然后调用ObservableValue.reportObserved方法,发现ObservableValue的属性和原型上都不存在该方法,于是从父类中查找,找到Atom的prototype中存在这个方法

Atom.prototype.reportObserved = function () {
   return reportObserved(this);
};
function reportObserved(observable) {
   checkIfStateReadsAreAllowed(observable);
   var derivation = globalState.trackingDerivation;
   if (derivation !== null) {
       if (derivation.runId !== observable.lastAccessedBy) {
           observable.lastAccessedBy = derivation.runId;
           derivation.newObserving[derivation.unboundDepsCount++] = observable;
           if (!observable.isBeingObserved) {
               observable.isBeingObserved = true;
               observable.onBecomeObserved();
           }
       }
       return true;
   }
   else if (observable.observers.size === 0 && globalState.inBatch > 0) {
       queueForUnobservation(observable);
   }
   return false;
}

(1)var derivation = globalState.trackingDerivation;这时候,在 Code-3-1-1-1-1 提到的将derivation赋值给全局变量,就用到了。 (2)还有一段非常重要的代码derivation.newObserving[derivation.unboundDepsCount++] = observable;,将observable(这里我更愿意说是ObservableValue实例)存进derivation的newObserving属性,这个将在后面介绍bindDependencies(derivation);会用到

三、依赖收集完成后,如何添加观察者?

在解读get拦截器的时候,已经将obervable存进derivation的newObserving属性,接下来看看

function bindDependencies(derivation) {
    var prevObserving = derivation.observing;
    var observing = (derivation.observing = derivation.newObserving);
    ...
   while (i0--) {
       var dep = observing[i0];
       if (dep.diffValue === 1) {
           dep.diffValue = 0;
           addObserver(dep, derivation);
       }
   }
   ...
}

将get拦截器中存入的newObserving赋值给observing,遍历循环observing数组,调用addObserver(dep, derivation)方法;

function addObserver(observable, node) {
   observable.observers.add(node);
   if (observable.lowestObserverState > node.dependenciesState)
       observable.lowestObserverState = node.dependenciesState;
}

参数observable就是属性值(即ObservableValue实例),参数node就是执行get拦截器之后生成的derivation,执行observable.observers.add(node);就完成了observable与derivation的关联,也就是mobx(5.14.0)源码解读一 observable结尾处提到的绿框中的observers赋值过程。

上图中,可以看到autorun执行之后,为$mobx.values[property]的observers属性绑定了reaction,而在autorun回调函数中,没有使用到的name属性的observers为空。

虽然已完成了observable和reaction的关联,但是就像demo中点击"+",再次触发autorun回调又是怎么一回事呢?我们将在下文中介绍。

总结

autorun主要做了以下几件事情:

(1)执行autorun方法,创建一个reaction实例,以reaction.schedule方法为切入点执行。

(2)第一次执行autorun回调函数,回调函数中有对proxy对象的读取,从而触发get拦截器,调用this.reportObserved

(3)往derivation(即reaction实例)的newObserving添加observableValue实例,若回调函数中存在多个对proxy对象的读取,就往newObserving添加多个observableValue实例

(4)执行bindDependencies(derivation),遍历newObserving,将derivation添加进$mobx.values[propertyName].observers中,如果一个属性被多个autorun读取,则往observers添加多个reaction实例。

其他文章

mobx(5.14.0)源码解读一 observable

mobx(5.14.0)源码解读三 observable属性自更新如何触发autorun回调