前言
在上文中,已经介绍了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赋值过程。

虽然已完成了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实例。