================前言===================html
本系列文章:react
=======================================git
寧靜的早上,執行官 MobX 將本身的計算性能優化機制報告呈現給警署最高長官。github
在這份報告解說中,談及部署成本最高的地方是在執行任務部分。所以優化這部分任務執行機制,也就至關於優化性能。segmentfault
警署最高長官瀏覽了報告前言部分,大體總結如下 2 點核心思想:性能優化
言外之意, 觀察組(觀察員)不在優化機制裏,他們的行爲仍舊循序漸進,該彙報的時候就彙報,該提供數據的時候提供數據。
那麼,執行人員依據什麼樣的規則來決定是否執行呢?微信
警署最高長官繼續往下閱讀,找到了解答該問題的詳細解說。簡言之,爲了解決該問題執行官 MobX 給出了狀態調整策略,並在這套策略之上指定的任務執行規則。函數
因爲專業性較強,行文解釋裏多處使用代碼。爲了更生動形象地解釋這套行爲規範,執行官 MobX 在報告裏採用 示例 + 圖示 的方式給出生動形象的解釋。性能
接下來咱們在 B. Source Code Time 部分詳細闡述這份 任務執行規則 的內容。測試
執行人員(探長和會計師)依據什麼樣的規則來決定是否執行呢?
答案是,執行官 MobX 提供了一個名爲 shouldCompute 的方法,每次執行人員(探長和會計師)須要執行以前都要調用該方法 —— 只有該方法返回 true
的時候纔會執行任務(或計算)。
在源碼裏搜索一下關鍵字shouldCompute
,就能夠知道的確只有 derivation(執行組,探長也屬於執行組)、 reaction(探長)、 computeValue(會計師)這些有執行權力的人才能調用這個方法,而 observerable(觀察員)並不在其中。
![]()
也就說 shouldCompute 就是任務執行規則,任務執行規則就是 shouldCompute。而背後支撐 shouldCompute 的則是一套 狀態調整策略
翻開 shouldCompute
源碼, 將會看到 dependenciesState
屬性。
其實這個 dependenciesState
(如下簡稱 D 屬性) 屬性還存在一個」孿生「屬性lowestObserverState
(如下簡稱 L 屬性)。這兩個屬性正是執行官 MobX 狀態調整策略的核心。
L 屬性 和 D 屬性反映當前對象所處的狀態, 都是枚舉值,且取值區間都是一致的,只能是如下 4 個值之一:
上面的文字表述比較枯燥,咱們來張圖感覺一下:
咱們以 「階梯」 來表示上述的狀態值;
依託L 屬性 和 D 屬性,執行官 MobX 的調整策略應運而生:
bankUser.income
屬性值),纔會啓用這套機制;下級成員擁有 L 屬性;而上級成員擁有 D 屬性,好比:
上述調整策略給咱們的直觀感覺,就是外界的影響致使 MobX 執行官的部署系統不穩定性上升,爲了消除這些不穩定,MobX 會盡量協調各方去執行任務,從而消除這些個不穩定性。
(舉個不甚恰當的例子,參考人類的免疫機制,病毒感冒後體溫上升就是典型的免疫機制激活的外在表現,抵禦完病毒以後體溫又迴歸正常)
咱們知道,只有上級成員(探長或者設計師)纔有執行任務的權力;而一旦知足上面的調整策略,在任什麼時候刻,執行官 MobX 直接查閱該上級成員的 D 屬性 就能判定該上級成員(探長或者設計師)是否須要執行任務了,很是簡單方便。
執行官 MobX 判斷的依據都體如今 shouldCompute 方法中了。
本人竊認爲這個shouldCompute
函數的名字太過於抽象,若是讓我命名的話,我更傾向於使用shouldExecuteTask
這個單詞。
依託L 屬性 和 D 屬性,執行任務規則(即 shouldCompute
)就出爐了:
執行任務規則看上去比較簡單,但應用到執行官 MobX 自動化部署方案中狀況就複雜了。下面將經過 3 個場景,從簡單到複雜,一步一步來演示L 屬性和D 屬性 是如何巧妙地融合到已有的部署方案中,並以最小的成本實現性能優化的。
var bankUser = mobx.observable({ income: 3, debit: 2 }); mobx.autorun(() => { console.log('張三的存貸:', income); }); bankUser.income = 4;
這裏咱們建立了 autorun
實例 (探長 R1)、observable
實例(觀察員O1)
這個示例和咱們以前在首篇文章《【用故事解讀 MobX源碼(一)】 autorun》中所用示例是一致的。
當執行 bankUser.income = 4;
語句的時候,觀察員 O1 觀察到的數值變化直接上報給探長 R1,而後探長就執行任務了。關係簡單:
從代碼層面上來說,該 響應鏈 上的關鍵函數執行順序以下:
(O1) reportChange -> (O1) propagateChanged -> (R1) onBecomeStale -> (R1) trackDerivedFunction -> fn(即執行 autorun 中的回調)
其中涉及到 L、D屬性 更改的函數有 propagateChanged
和 track
這兩個。
Step 1:在 propagateChanged 方法執行時,讓觀察員 O1 的 L 屬性 從 0 → 2 ,按照上述的調整原則,探長 R1 的 D屬性 必需要高於觀察員 O1 的 L 屬性,因此其值也只能用從 0 → 2。
Step 2:而隨着 trackDerivedFunction 方法的執行(即探長執行任務)後,觀察員 O1 的 L 屬性 又從 2 → 0,同時也讓探長 R1 的 D屬性 從 2 → 0;
在這裏咱們已經能夠明顯感覺到 非穩態的上升 和 削減 這兩個階段:
bankUser.income
屬性,觸發 propagateChanged
方法,從而讓觀察員的 L 屬性 以及探長的 D屬性 都變成了 2 ,這是系統趨向不穩定的表現。從 層級上來看,是自下而上的過程。onBecameStale
方法。執行期間 MobX 執行官查閱探長的 D屬性 是 2,依據 shouldCompute
中的執行規定,贊成讓探長執行任務。執行完以後,觀察員的 L 屬性、探長的 D屬性 都降低爲 0,表示系統又從新回到穩定狀態。從 層級上來看,是自上而下的過程。上面介紹了最簡單的狀況,只有一個探長 R1(autorun
)和一個觀察員 O1(income
)。
如今咱們將環境稍微弄複雜一些,新增一個 會計師 C1(divisor
) ,此時再來看看上述的變動原則是如何在系統運轉時起做用的:
var bankUser = mobx.observable({ income: 3, debit: 2 }); var divisor = mobx.computed(() => { return bankUser.income / bankUser.debit; }); mobx.autorun(() => { console.log('張三的 divisor:', divisor); }); bankUser.income = 4;
這個示例和咱們以前在首篇文章《【用故事解讀 MobX源碼(二)】 computed 》中所用示例是一致的。
當咱們執行 bankUser.income = 4;
語句的時候,觀察員 O1 先上報給會計師 C1,接着會計師 C1 會從新執行計算任務後,上報給探長,探長R1 再從新執行任務。
上面描述起來比較簡單,但從代碼層面上來說仍是有些繞,先列出該 響應鏈 上的關鍵函數執行順序以下(很明顯比上面的示例要稍微複雜一些):
(O1) reportChange -> (O1) propagateChanged -> (C1) propagateMaybeChanged -> (R1) onBecomeStale(這裏並不會讓探長 `runReaction`) -> (O1) endBatch -> (R1) runReaction(到這裏才讓探長執行 `runReaction`) -> (C1) reportObserved -> (C1) shouldCompute -> (C1) trackAndCompute -> (C1) propagateChangeConfirmed -> (R1) trackDerivedFunction -> fn(即執行 autorun 中的回調)
注:這裏還須要囉嗦一句,雖然這裏會觸發探長 R1 的
onBecomeStale
方法,但 MobX 並不會直接讓探長執行任務,這也是 MobX 優化的一種手段體現,詳細分析請移步《
【用故事解讀 MobX源碼(二)】 computed 》。
Step 1:在 propagateChanged 方法執行時,讓觀察員 O1 的 L 屬性 從 -1 → 2 ,按照上述的調整原則,其直接上級 C1 的 D屬性 必需要高於觀察員 O1 的 L 屬性,因此其值也只能用從 0 → 2;
和上述簡單示例中最大的不一樣,在於該期間還涉及到會計師 C1 的狀態更改,具體表現就是調用 propagateMaybeChanged ,在該方法執行後讓會計師 C1 的 L 屬性 從 0 → 1 ,其直接上級 R1 的 D屬性 必需要高於會計師 C1 的 L 屬性,因此其值也從 0 → 1;
注:雖然觀察員 O1 的狀態更改 不能直接 觸發探長 R1 的狀態更改,卻能夠憑藉會計師 C1 間接 地讓 探長 R1 的狀態發生更改。
Step 2:此步驟是以 會計師 狀態變動爲中心演變過程,上一個案例並不存在會計師,因此並不會有該步驟。經過 trackAndCompute 方法,會計師 C1 的 D 屬性 又從 2 → 0,同時也讓觀察員 O1 的 L屬性 從 2 → 0;這個過程代表會計師 C1 的計算值已經更新了。
隨後在 propagateChangeConfirmed 中讓探長 R1 的 D 屬性 從 1 (下級數值可能有更新)→ 2 (肯定下級數值肯定有更新),同時也讓會計師 C1 的 L 屬性 從 1(告知上級本身的值可能有更新)→ 2 (告知上級本身的值的確有更新);代表探長 R1 和 會計師 C1 的穩態還未達成,須要 Step 3 的執行去消除非穩態。
Step 3:會計師的計算值 C1 更新完畢以後,探長才執行任務。經過 trackDerivedFunction 方法的執行(即探長執行任務)後,會計師 C1 的 L 屬性 又從 2 → 0,同時也讓探長 R1 的 D 屬性 從 2 → 0;
雖然這個示例中,狀態的變動比上面的示例要複雜一些,不過咱們依然能夠從總體上感覺到 非穩態的上升 和 削減 這兩個階段:
bankUser.income
屬性,觸發 propagateChanged
方法,從而讓觀察員 O1 的 L 屬性 以及會計師 C1 的 D屬性 都變成了 2 ,同時讓會計師 C1 的 L 屬性 以及探長 R1 的 D屬性 都變成了 1 。這是系統趨向不穩定的表現。從 層級上來看,是自下而上的過程。咱們繼續在上一個示例上修改,再新增一個計算值 indication
(這個變量的建立沒有特殊的含義,純粹是爲了作演示),由會計師 C2 了負責其進行計算。
var bankUser = mobx.observable({ income: 3, debit: 2 }); var divisor = mobx.computed(() => { return bankUser.income / bankUser.debit; }); var indication = mobx.computed(() => { return divisor / (bankUser.income + 1); }); mobx.autorun(() => { console.log('張三的 indication', indication); }); bankUser.debit = 4;
大致成員和以前的示例相差不大,只是此次咱們修改 bankUser.debit
變量(前面兩個示例都是修改 bankUser.income
)。
這麼作的目的是爲了營造出下述的 響應鏈 結構,咱們經過修改 bankUser.debit
變量,從而影響 會計師 C1,繼而影響 會計師 C2,最終讓探長 R1 執行任務。
一樣的,咱們從代碼層面上來列出該 響應鏈 上的關鍵函數執行順序,比上兩個示例都複雜些,大體以下:
(O2) reportChange -> (O2) propagateChanged -> (C1) propagateMaybeChanged -> (C2) propagateMaybeChanged -> (R1) onBecomeStale(這裏並不會讓探長 `runReaction`) -> (O2) endBatch -> (R1) runReaction(到這裏才讓探長執行 `runReaction`) -> (R1) shouldCompute -> (C2) shouldCompute -> (C1) shouldCompute -> (C1) trackAndCompute -> (C1) propagateChangeConfirmed -> (C2) trackAndCompute -> (C2) propagateChangeConfirmed -> trackDerivedFunction -> fn(即執行 autorun 中的回調)
Step 1:在 propagateChanged 方法執行時,讓觀察員 O1 的 L 屬性 從 0 → 2 ,按照上述的調整原則,其直接上級 C1 的 D屬性 必需要高於觀察員 O1 的 L 屬性,因此其值也只能用從 0 → 2;
該期間還涉及到會計師 C一、C2 的狀態更改,具體表現就是調用 propagateMaybeChanged ,在該方法執行後讓會計師 C一、C2 的 L 屬性 從 0 → 1 ,他們各自的直接上級 C二、 R1 的 D屬性 值也從 0 → 1;
描述起來比較複雜,其實無非就是多了一個 會計師 C2 的 propagateMaybeChanged
方法過程,一圖勝千言:
Step 2:此步驟是以 會計師 狀態變動爲中心演變過程,該步驟是上一個示例中 Step 2 的「複數」版,多我的參與就複雜些,不過條理仍是清晰明瞭的。上個示例中只有一個會計師,因此 trackAndCompute ->propagateChangeConfirmed 的過程只有一次,而這裏有兩個會計師,因此這個過程就有兩次(下圖中兩個藍框);
通過該步驟以後會計師 O二、C1 的 L 屬性 又從 2 → 0,同時也讓C一、C2 的 D 屬性 從 2 → 0;這個過程代表觀察員 O1 和 會計師 C1 的計算值已經更新,達到穩態。
而 C2 的 L 屬性 、探長 R1 的 D 屬性 又從 0 → 2,代表探長 R1 和 會計師 C2 的穩態還未達成,須要 Step 3 的執行去消除非穩態。
Step 3:探長執行任務,經過 trackDerivedFunction 方法的執行(即探長執行任務)後,會計師 C2 的 L 屬性 又從 2 → 0,同時也讓探長 R1 的 D 屬性 從 2 → 0;這一步和上個示例中的 Step 3 幾乎相同。
在這個示例中,狀態的變動縱使比上面的示例要複雜得多,但咱們仍是很清晰地從總體上感覺到 非穩態的上升 和 削減 這兩個階段:
bankUser.debit
屬性,觸發 propagateChanged
方法,從而讓觀察員 O1 開始,依次影響 會計師 C一、C2,以及探長 R1 的 L、D 屬性從 0 變成 1 或者 2,這是系統趨向不穩定的表現。從 層級上來看,是自下而上的過程。經過上面三個從簡單逐步到複雜的示例,咱們簡單總結概括一下 MobX 在處理狀態變動過程當中所採起執行機制以及其背後的調整策略:
在軟件設計中,爲了更好地顯示這種狀態變動和事件之間的關係,經常使用 狀態圖 來展示(沒錯,就是 UML建模中的那個狀態圖)
若是不太熟悉,這裏給個參考文章 UML建模之狀態圖(Statechart Diagram) 方便查閱。
挨個總結上述 3 個案例中 L、D屬性,咱們將其中的事件和屬性改變抽離出來,就能獲取狀態圖了,方便咱們從另一個角度理解和體會。
Observable(觀察員)、ComputeValue(會計師)這兩種類型擁有 L 屬性 :
Reaction(探長)、ComputeValue(會計師)這兩種類型擁有 D 屬性:
因此,會計師同時擁有 L屬性 和 D 屬性
若是咱們將 2.三、有兩個會計師的狀況 示例中的 bankUser.debit = 4;
修改爲 bankUser.income = 6;
的話,那各個成員對象的 D 屬性、L 屬性 的變化狀況又是怎麼樣的?
如何在複雜的場景下兼顧計算性能?
MobX 提供了 shouldCompute
方法用於直接判斷是否執行計算(或任務),判斷的依據很是簡單,只要根據對象的 dependenciesState
屬性是否爲 true
就能直接做出判斷。
而其背後的支持則是 dependenciesState
屬性(上文中的 D 屬性)和 lowestObserverState
(上文中的 L 屬性),這兩個屬性依託 MobX 中自動化機制在適當時機(搭」順風車「)進行變動。所以,不管多麼複雜的場景下 MobX 能以低廉的成本兼顧性能方面的治理,充分運用惰性求值思想減小計算開銷。
初看 MobX 源碼,它每每給你一種 」雜項叢生「的感受(調試這段代碼的時候真是內心苦啊),但其實在這背後運轉着一套清晰的 非穩態傳遞 和 非穩態削減 的固定模式,一旦掌握這套模式以後,MobX 自動化響應體系的脈絡已清晰可見,這將爲你更好理解 MobX 的運行機制打下紮實的基礎。
到本篇爲止,咱們已經耗費 3 篇文章來解釋 MobX 的(絕大部分)自動化響應機制。通過這 3 篇文章,讀者應該對 MobX 的整個運起色制有了一個比較清晰明瞭的理解。後續的文章中將逐漸縮減」故事「成分,將講解重心轉移到 MobX 自己概念(好比 Observable
、decorator
、Atom
等)源碼的解讀上,相信有了這三篇文章的做爲打底,理解其他部分更多的是在語法層面,閱讀起來將更加遊刃有餘。
下面的是個人公衆號二維碼圖片,歡迎關注,及時獲取最新技術文章。