咱們在Mobx 源碼解析 一(observable)已經知道了observable 作的事情了, 可是咱們的仍是沒有講解明白在咱們的Demo中,咱們在Button
的Click
事件中只是對bankUser.income
進行了自增和自減,並無對incomeLabel
進行操做, 可是incomeLabel
的內容卻實時的更新了, 咱們分析只有在mobx.autorun
方法中對其的innerText
進行了處理, 因此很容易理解神祕之處在於此方法,接下來咱們來深刻分析這個方法的實現原理.node
在Git 上面建立了一個新的autorun分支, 對Demo 的代碼進行小的變動,變動的主要是autorun 方法:react
const incomeDisposer = mobx.autorun(() => {
if (bankUser.income < 0) {
bankUser.income = 0
throw new Error('throw new error')
}
incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}`
}, {
name: 'income',
delay: 2*1000,
onError: (e) => {
console.log(e)
}
})
複製代碼
能夠看出,咱們給autorun 方法傳遞了第二個參數, 並且是一個Object :git
{
name: 'income',
delay: 2*1000,
onError: (e) => {
console.log(e)
}
複製代碼
咱們能夠根據這三個屬性能夠猜想出:github
autorun
方法.autorun源碼以下:數組
export function autorun(view, opts = EMPTY_OBJECT) {
if (process.env.NODE_ENV !== "production") {
invariant(typeof view === "function", "Autorun expects a function as first argument");
invariant(isAction(view) === false, "Autorun does not accept actions since actions are untrackable");
}
const name = (opts && opts.name) || view.name || "Autorun@" + getNextId();
const runSync = !opts.scheduler && !opts.delay;
let reaction;
if (runSync) {
// normal autorun
reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
}
else {
const scheduler = createSchedulerFromOptions(opts);
// debounced autorun
let isScheduled = false;
reaction = new Reaction(name, () => {
if (!isScheduled) {
isScheduled = true;
scheduler(() => {
isScheduled = false;
if (!reaction.isDisposed)
reaction.track(reactionRunner);
});
}
}, opts.onError);
}
function reactionRunner() {
view(reaction);
}
reaction.schedule();
return reaction.getDisposer();
}
複製代碼
查看這個方法,發現其能夠傳遞兩個參數:bash
- view, 必須是一個function, 也就是咱們要執行的業務邏輯的地方.
- opts, 是一個可選參數, 並且是一個Object, 能夠傳遞的屬性有四個
name
,scheduler
,delay
,onError
, 其中delay和scheduler 是比較重要的兩個參數,由於決定是否同步仍是異步.- 查看這個方法的最後第二行
reaction.schedule();
, 其實表示已經在autorun 方法調用時,會當即執行一次其對應的回調函數
在上面的梳理中發現, 若是傳遞了delay
或者scheduler
值,其進入的是else
邏輯分支,也就是異步處理分支,咱們如今先將demo 中的delay: 2*1000,
屬性給註釋, 先分析同步處理的邏輯( normal autorun 正常的autorun)異步
首先建立了一個 Reaction 是實例對象,其中傳遞了兩個參數: name 和一函數, 這個函數掛載在一個叫onInvalidate
屬性上,這個函數最終會執行咱們的autorun
方法的第一個參數viwe
, 也就是咱們要執行的業務邏輯代碼:函數
reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
複製代碼
function reactionRunner() {
view(reaction);
}
複製代碼
咱們看到,實例化reaction
對象後,當即執行了其schedule
方法,而後就只是返回一個對象reaction.getDisposer()
對象, 整個autorun
方法就結束了。post
autorun
方法看起來很簡單,可是爲何能在其對應的屬性變動時,就當即執行view
方法呢, 其奧妙應該在於schedule
方法中,因此咱們應該進一步分析這個方法.ui
schedule() {
if (!this._isScheduled) {
this._isScheduled = true;
globalState.pendingReactions.push(this);
runReactions();
}
}
複製代碼
globalState.pendingReactions.push(this);
將當前實例放在一個全局的數組中globalState.pendingReactions
const MAX_REACTION_ITERATIONS = 100;
let reactionScheduler = f => f();
export function runReactions() {
if (globalState.inBatch > 0 || globalState.isRunningReactions)
return;
reactionScheduler(runReactionsHelper);
}
function runReactionsHelper() {
globalState.isRunningReactions = true;
const allReactions = globalState.pendingReactions;
let iterations = 0;
while (allReactions.length > 0) {
if (++iterations === MAX_REACTION_ITERATIONS) {
allReactions.splice(0); // clear reactions
}
let remainingReactions = allReactions.splice(0);
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction();
}
globalState.isRunningReactions = false;
}
複製代碼
globalState.inBatch > 0 || globalState.isRunningReactions
是否有在運行的reaction.const allReactions = globalState.pendingReactions;
(咱們在schedule
方法分析中,在這個方法,將每個reaction 實例放到這個globalState 數組中)runReaction
方法( remainingReactions[i].runReaction();
)globalState.isRunningReactions = false;
這樣就能夠保證一次只有一個autorun
在運行,保證了數據的正確性咱們分析了基本流程,最終執行的是在Reaction 實例方法runReaction
方法中,咱們如今開始分析這個方法。
runReaction() {
if (!this.isDisposed) {
startBatch();
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;
try {
this.onInvalidate();
if (this._isTrackPending &&
isSpyEnabled() &&
process.env.NODE_ENV !== "production") {
spyReport({
name: this.name,
type: "scheduled-reaction"
});
}
}
catch (e) {
this.reportExceptionInDerivation(e);
}
}
endBatch();
}
}
複製代碼
startBatch();
只是設置了globalState.inBatch++;
this.onInvalidate();
關鍵是這個方法, 這個方法是實例化Reaction 對象傳遞進來的,其最終代碼以下:reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
複製代碼
function reactionRunner() {
view(reaction);
}
複製代碼
因此this.onInvalidate
其實就是:
function () {
this.track(reactionRunner);
}
複製代碼
上面咱們已經分析了autorun 的基本運行邏輯, 咱們能夠在this.track(reactionRunner);
地方,打個斷點, 查看下function 的call stack.
- derivation,就是autorun 方法建立的Reaction 實例
- f, 就是autorun的回調函數, 也就是derivation的onInvalidate 屬性
咱們查看到result = f.call(context);
,很明顯這個地方是就是執行autorun方法回調函數的地方。
咱們看到在這個方法中將當前的derivation
賦值給了globalState.trackingDerivation = derivation;
,這個值在其餘的地方會調用。 咱們再回過頭來看下autorun 的回調函數究竟是個什麼:
const incomeDisposer = autorun((reaction) => {
incomeLabel.innerText = `${bankUser.name} income is ${bankUser.income}`
})
複製代碼
在這裏,咱們調用了bankUser.name
, bankUser.income
,其中bankUser 是一個被observable 處理的對象,咱們在Mobx 源碼解析 一(observable)中知道, 這個對象用Proxy 進行了代理, 咱們讀取他的任何屬性,都會鍵入攔截器的get 方法,咱們接下來分析下get 方法到底作了什麼。
get 方法的代碼以下:
get(target, name) {
if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
return target[name];
const adm = getAdm(target);
const observable = adm.values.get(name);
if (observable instanceof Atom) {
return observable.get();
}
if (typeof name === "string")
adm.has(name);
return target[name];
}
複製代碼
在Mobx 源碼解析 一(observable) 中咱們知道,observable 是一個ObservableValue 類型, 而ObservableValue 又繼承與Atom, 因此代碼會走以下分支:
if (observable instanceof Atom) {
return observable.get();
}
複製代碼
咱們繼續查看其對應的get 方法
get() {
this.reportObserved();
return this.dehanceValue(this.value);
}
複製代碼
這裏有一個關鍵的方法: this.reportObserved();, 顧名思義,就是我要報告我要被觀察了,將observable 對象和autorun 方法給關聯起來了,咱們能夠繼續跟進這個方法。
經過斷點,咱們發現,最終會調用observable.js 的reportObserved方法。
其方法的具體代碼以下,咱們會一行行的進行分析export function reportObserved(observable) {
const 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;
}
複製代碼
const derivation = globalState.trackingDerivation;
這行代碼和容易理解,就是從globalstate 取一個值,可是這個值的來源很重要, 上面咱們在derivation.js 的trackDerivedFunction 方法中,發現對其賦值了globalState.trackingDerivation = derivation;
。而其對應的值derivation
就是對應的autorun 建立的Reaction 對象derivation.newObserving[derivation.unboundDepsCount++] = observable;
這一行相當重要, 將observable對象的屬性和autorun 方法真正關聯了。在咱們的autorun 方法中調用了兩個屬性,因此在執行兩次get 方法後,對應的globalState.trackingDerivation值以下圖所示:
其中newObserving 屬性中,有了兩個值,着兩個值,表示當前的這個autorun 方法,會監聽這個兩個屬性,咱們接下來會解析,怎麼去處理newObserving數組
咱們繼續來分析trackDerivedFunction 方法
export function trackDerivedFunction(derivation, f, context) {
changeDependenciesStateTo0(derivation);
derivation.newObserving = new Array(derivation.observing.length + 100);
derivation.unboundDepsCount = 0;
derivation.runId = ++globalState.runId;
const prevTracking = globalState.trackingDerivation;
globalState.trackingDerivation = derivation;
let 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;
}
複製代碼
上面咱們已經分析完了result = f.call(context);
這一步驟, 咱們如今要分析: bindDependencies(derivation);方法
參數derivation ,在執行每一個屬性的get 方法時, 已經給derivatio 的newObserving 屬性添加了兩條記錄, 如圖:
咱們接下來深刻分析bindDependencies 方法,發現其對newObserving 進行了遍歷處理,以下
while (i0--) {
const dep = observing[i0];
if (dep.diffValue === 1) {
dep.diffValue = 0;
addObserver(dep, derivation);
}
}
複製代碼
addObserver(dep, derivation);
,由方法名猜測,這個應該是去添加觀察了,咱們查看下具體代碼:
export function addObserver(observable, node) {
observable.observers.add(node);
if (observable.lowestObserverState > node.dependenciesState)
observable.lowestObserverState = node.dependenciesState;
}
複製代碼
參數: observable 就是咱們每一個屬性對應的ObservableValue, 有一個Set 類型的observers 屬性 , node就是咱們autorun 方法建立的Reaction 對象
observable.observers.add(node); 就是每一個屬性保存了其對應的觀察者。
其最終將observable 的對象加工成以下圖所示(給第三步的observes 添加了值):
咱們已經知道observable 對象和autorun 方法已經關聯起來,咱們後續會繼續分析,當改變observable 屬性的值的時候,怎麼去觸發autorun 的回調函數。我如今的猜測是:首先確定會觸發Proxy 的set方法,而後set方法會遍歷調用observers 裏面的Reaction 的onInvalidate 方法,只是猜測,咱們後面深刻分析下。