================前言===================javascript
本系列文章:html
=======================================java
在寫本文的時候,因爲 MobX 以及升級到 4.x,API 有較大的變化,所以後續的文章默認都將基於 4.x 以上版本進行源碼閱讀。react
前一篇文章仍然以 mobx v3.5.1 的源碼,autorun
邏輯在新版中沒有更改,所以源碼邏輯仍舊一致。git
爲了多維度掌控嫌疑犯的犯罪特徵數據,你(警署最高長官)想要獲取並實時監控張三的 貸款數額、存貸比(存款和貸款二者比率) 的變化。github
因而你就擬定了新的命令給執行官 MobX:segmentfault
var bankUser = mobx.observable({ income: 3, debit: 2 }); var divisor = mobx.computed(() => { return bankUser.income / bankUser.debit; }); mobx.autorun(() => { console.log('張三的貸款:', bankUser.debit, ';張三的存貸比: ' + divisor); });
相比上一次的命令,除了監控張三貸款這項直接的指標,還須要監控 貸款比(divisor
) 這項間接指標。性能優化
執行官 MobX 稍做思忖,要完成這個任務比以前的要難一點點,須要費一點兒精力。微信
不過,這也難不倒能力強大的 MobX 執行官,一番策略調整以後,從新拿出新的執行方案。部署實施以後,當張三去銀行存款、貸款後,這些變化都實時反饋出來了:ide
此次的部署和前一次相差不大,除了須要讓觀察員 O2(監視 income
)參與進來以外,考慮到警署最高長官所需的 存貸比 (divisor
),還得派出另外一類職員 —— 會計師:
會計師是一個頗有意思的角色,要想理解他們,必須得思考他們的數據「從哪兒來?到哪裏去?」 這兩個問題:
引入了會計師角色以後,MobX 執行官從新繪製了部署計劃圖:
解釋一下此計劃圖的意思:
debit
)和存貸比(divisor
):() => { console.log('張三的貸款:', bankUser.debit, ';張三的存貸比: ' + divisor); }
bankUser.income
屬性和 bankUser.debit
屬性;由於仍是 autorun
命令,因此仍然執行 A計劃方案(詳情參考上一篇《【用故事解讀 MobX源碼(一)】 autorun》)MobX 執行官的部署方案從總體上看是同樣的,考慮到多了會計師這個角色的參與,因此特地在探長 獲取存貸比(divisor
) 邏輯處空出一部分留給會計師讓它自由發揮:
這樣作,MobX 執行官也爲了在實際行動中向他的警署長官證明該 A計劃方案 的確擁有「良好的擴展性」。
解開這層新增的會計師計算邏輯 「面紗」,圖示以下:
你會發現歷史老是驚人的類似,新增的會計師執行計算任務的邏輯其實 探長 執行任務的邏輯是同樣的,下圖中我特地用 相同的序號(不一樣的顏色形狀)標示 出,序號所對應含義以下:
此執行計算任務的邏輯,若是不告訴觀察員的話,觀察員還覺得又來了一名「探長」上級。?
從部署圖裏咱們能夠看出會計師具備兩面性;
自從有了會計師的參與,探長仍是那個探長,但他的下級已經不是以前的下級了。藉助 A計劃任務的執行,會計師 C1 在上報計算值的時候,會順水推舟地執行計算任務,同時更新他的 」關係網「。
會計師有一個特性就是比較懶:就算觀察員所觀察到的值變動了,他們也不會當即從新計算,而只在必要的時候(好比當上級前來索取時)纔會從新計算。
舉個例子,當觀察員 O1 發現張三的帳戶存款從原來的 3 變成 6 :
bankUser.income = 6;
這個時候會觸發一系列的 「漣漪」:
income
)有變動將上面的文字轉換成流程圖,能夠清晰看到各角色在此次「漣漪」中所起到的做用:
這裏須要注意 3 點:
會計師這種拖延到 只有被須要的時候才進行計算 的行爲,有沒有讓你回憶起學生時代寒假結束前一天瘋狂補做業的場景??
當執行官 MobX 拿着這份執行報告送達給你(警署最高長官),閱覽完畢:」不錯,這套方案的確部分證明了你以前所言的可擴展性。但隨着職員的引入,運起色構逐漸龐大,如何避免沒必要要的開銷的呢?「
」長官您高瞻遠矚,這的確是一個問題。在井井有理的規則下,個別職員的運做效率的確會打折扣。所以避免職員沒必要要的計算開銷,也是在我方案部署規劃以內。正如您所見,上述方案中會計師的‘惰性’、探員在事務以後再進行任務等機制,都是基於優化性能所採起的措施。「 執行官 MobX 稍做停頓,繼續道,」爲了更好地闡述這套運行方案的性能優化機制,我明天呈上一份報告,好讓您得以全面瞭解。「
」Good Job!期待你的報告「。
那麼,執行官 MobX 是憑藉什麼機制減小開銷的呢?且聽下回分解。
(本節完,未完待續)
本節部分,仍然是就着上面的」故事「來說 MobX 中的源碼。
先羅列本文故事中新出現的 會計師 角色與 MobX 源碼概念映射關係:
故事人物 | MobX 源碼 | 解釋 |
---|---|---|
會計師 | computedvalue | 官方文檔 - (@)computed 計算值 |
探長、執行官等角色的映射關係,參考上一篇《 【用故事解讀 MobX源碼(一)】 autorun》
本文的重點內容就是 computedvalue 的部分源碼(它在 autorun
等場景中的應用)
autorun
(A 計劃)的源碼在上一節講過,這裏再也不贅述。咱們僅僅講解一下 computedValue 在 autorun
中的表現。
在故事中咱們講到過,當探長向會計師索要計算值的時候,此時懶惰的會計師爲了 」應付交差「,這時候纔開始計算,其計算的過程和探長執行的任務流程幾乎一致。
從源碼角度去看一下其中的緣由。
當探長執行任務:
() => { console.log('張三的貸款:', bankUser.debit, ';張三的存貸比: ' + divisor); }
任務中也涉及 bankUser.debit
變量和 divisor
變量;其中在獲取 bankUser.debit
變量之時會讓觀察員 O2 觸發 reportObserved
方法,這個上一篇文章着重講過,此處就不詳細展開了;而請求 divisor
數值的時候,則會觸發該值的 valueOf()
方法 —— 即調用會計師(computedValue)的 valueOf()
方法。
爲何調用就觸發 valueOf()
方法呢?請看下方的「知識點」備註?
======== 插播知識點 =========任何原始值仍是對象其實都包含
valueOf()
或toString()
方法,valueOf()
會返回最適合該對象類型的原始值,toString()
將該對象的原始值以字符串形式返回。
這兩個方法通常是交由 JS 去隱式調用,以知足不一樣的運算狀況。好比在數值運算(如a + b
)裏會優先調用valueOf()
,而在字符串運算(如alert(c
))裏,會優先調用toString()
方法
順帶附上兩篇 參考文章
======== 完畢 ==========
一旦調用調用會計師的 valueOf 方法:
valueOf(): T { return toPrimitive(this.get()) }
其實就是調用 this.get() 方法,咱們瞧一眼源碼;
這裏有個分叉點,根據 globalState.inBatch
決定究竟是啓用 重量級計算 仍是 輕量級計算:
globalState.inBatch
值大於 0,說明會計師被上級徵調(處於上級事務中),好比此案例中,陷於 A 計劃(autorun
)的會計師,在上級探長 R1 須要查閱計算值時候,就會進入重量級計算模式globalState.inBatch
值爲 0,就會進入輕量級計算模式,簡化計算的邏輯。但不管輕量級仍是重量級計算,都會涉及到調用 computeValue() 方法來執行計算任務。
調用的時候,若是是 重量級計算 則 track
這個 bool 值爲 true,不然track
值爲 false。
計算值有個屬性,this.derivation
就是會計師要計算數值時所依據的計算表達式,也就是而咱們定義會計師時所傳入的匿名函數:
() => { return bankUser.income / bankUser.debit; }
不管是 重量級計算 模式仍是 輕量級計算 模式,最終都是會調用該計算表達式獲取計算值。
重量級計算 模式和 輕量級計算 模式二者的差異只是在於前者在執行該計算表達式以前會設置不少環境,後者直接就按這個表達式計算數值返回。
在上述的故事中,因爲探長 R1 人物的存在,會計師會執行 重量級計算 模式,接下來的源碼分析也走這條分支路線。( 輕量級計算 模式的狀況當作課後思考題)。
在 重量級計算的時候,computeValue(true)
就會走和 探長 操做模式同樣 trackDerivedFunction
步驟。沒錯,探長和會計師調用的就是同一個方法,因此他們在執行任務的時候,行爲痕跡是同樣的,沒毛病。
若是忘記
trackDerivedFunction
方法內容,請查看 《【用故事解讀 MobX源碼(一)】 autorun》的 」2.2.二、trackDerivedFunction「 部分
只不過會計師只能執行計算類的任務(純函數)罷了,探長能夠執行任意類型的任務。
和探長同樣,會計師執行計算任務完畢以後調用 bindDependencies
將綁定 觀察員 O1 和 觀察員 O2 ;而在執行計算以後,會計師會調用 propagateChangeConfirmed
方法,更改本身和上級 探長 的狀態 —— 這說明,對探長而言,會計師就至關於 觀察員的角色,在探長執行任務結束後像觀察員同樣須要上報本身的計算值,並和 探長 取得聯繫;
這麼看會計師還真 」牆頭草,兩邊倒」。
至此,會計師這個角色以較低的成本就能完美地整合進執行官 MobX 所部署的 A 集合部署方案中。??
一旦張三的帳戶存款(income
)發生變化,將會觸發 MobX 所提供的 reportChanged 方法:
public reportChanged() { startBatch() propagateChanged(this) endBatch() }
注意這裏的startBatch
和endBatch
方法,說明觀察員 O1 發起事務了。
咱們知道(不知道的請閱讀上一篇文章)該 reportChanged()
方法中的 propagateChanged()
會觸發上級的 onBecomeStale()
方法。
觀察員 O1 此時的上級是 會計師 C1,其所定義的 onBecomeStale 以下:
onBecomeStale() { propagateMaybeChanged(this) }
看一下 propagateMaybeChanged(this) 源碼,也比較簡單,主要作了兩件事情,① 會計師會調整自身的狀態; ②而後觸發其上級(探長 R1)的 onBecomeStale()
方法。
可見觀察員 01 會引發會計師 C1 的響應,而會計師會引發探長 R1 的響應,這種響應「漣漪」就是經過下級觸發上級的 onBecomeStale
方法造成的連鎖反應。
不一樣上級(好比會計師和探長)的
onBecomeStale
定義不一樣。
探長的這個 onBecomeStale
方法在上一篇文章的 「三、響應觀察值的變化 - propagateChanged」 中咱們講過,探長將請求 MobX 請求從新執行一遍 A 計劃方案。
然而,MobX 拒絕了此次請求,讓他再等待一下。??
這是由於在 runReactions 方法中:
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
因爲此時 inBatch
是 1(由於觀察員執行了 startBatch()
),因此會直接 return 掉。
直到觀察員執行 endBatch() 的時候,除了會結束本次的上報事務,同時執行官 MobX 會從新執行 runReactions
方法,讓久等的探長去執行任務:
探長在執行任務的時候,就會打印張三的貸款(debit
)、存貸比(divisor
)了。
綜上,當張三存款(income
)變動,就能讓 A 計劃(autorun
)自動運行,探長會打印張三的貸款(debit
)、存貸比(divisor
)。
這裏須要說起一下,關於會計師從新計算的時機,是在探長執行 shouldCompute 的時候,探長髮現會計師值 陳舊 了,就讓會計師從新計算:
看看這裏,對計算值而言,isComputedValue()
(若是是計算值)返回 true,就會執行 obj.get()
方法,這個方法剛纔剛講過,會讓會計師執行 重量型計算操做,更新本身的計算值。
因此,此次計算時機並不是等到探長執行任務時(真正用到該值)的時候才讓其從新計算,和第一次 autorun
的時機不一致。
估計這是 MobX 考慮到會計師的值確定須要更新的(已經肯定要被探長 R1 用到),還有可能會被其餘上級引用,既然早晚要更新的,那就儘量將更新前置,這樣在總體上能下降成本。
更新完以後,在探長執行任務的時候,會計師彙報本身是最新的值了,就不用再從新計算一遍。
雖然懶,可是懶得有技巧。
至此,有關會計師的源碼解讀已經差很少,後續有想到的再補充。
本文爲了方便說明,因此單獨使用 mobx.computed
方法定義計算值,平時使用中更多則是直接應用在 對象中屬性 上,使用 get 語法:
var bankUser = mobx.observable({ income: 3, debit: 2, get divisor() { return this.income / this.debit; } });
這僅僅是寫法上不同,源碼分析的思路是一致的。
問題:當咱們更改張三貸款數額 bankUser.debit = 4;
時,請從源碼角度解答 MobX 的執行流程是如何的?
參考答案提示:
reportChanged() => propagateChanged() => propagateMaybeChanged() => runReaction() => track() => get() => computeValue() => bindDependencies()
問題:若是不存在 autorun
(即沒有探長參與,僅有觀察員和會計師),此時僅改變張三存款數值:
var bankUser = mobx.observable({ income: 3, debit: 2 }); var divisor = mobx.computed(() => { return bankUser.income / bankUser.debit; }); bankUser.income = 6; // 請問此時的執行狀況是什麼樣的? console.log('張三的存貸比:', divisor)
請問會計師會從新計算數值麼?此時這套系統的執行狀況又會是怎麼樣的呢?
參考答案提示:會計師此時執行 輕量級計算模式。
此篇文章講解 MobX 中 計算值 (computedValue) 的概念,類比故事中的會計師角色。總結一下 計算值 (computedValue)的特徵:
autorun
(或reaction
) 很像,之因此類似是在 執行任務 時都涉及到調用 trackDerivedFunction
方法;而對 autorun
(或reaction
)而言,計算值和觀察值很相,都是數據提供者。正如 官方文檔 而言,計算值是高度優化過的,因此儘量應用他們。
下一篇文章將探討 MobX 中與 autorun
和 computed
相關的計算性能優化的機制,看看 MobX 如何平衡複雜場景下狀態管理時的效率和性能。
下面的是個人公衆號二維碼圖片,歡迎關注,及時獲取最新技術文章。