摘要:隨后,執(zhí)行官給出一張當(dāng)張三存款發(fā)生變化之時(shí),此機(jī)構(gòu)的運(yùn)作時(shí)序圖的確,小機(jī)構(gòu)靠人力運(yùn)作,大機(jī)構(gòu)才靠制度運(yùn)轉(zhuǎn)。第一條語(yǔ)句創(chuàng)建觀察員第一條語(yǔ)句張三我們調(diào)用的時(shí)候,就創(chuàng)建了對(duì)象,對(duì)象的所有屬性都將被拷貝至一個(gè)克隆對(duì)象并將克隆對(duì)象轉(zhuǎn)變成可觀察的。
================前言===================
初衷:網(wǎng)上已有很多關(guān)于 MobX 源碼解讀的文章,但大多閱讀成本甚高。本人在找文章時(shí)對(duì)此深有體會(huì),故將以系列故事的方式展現(xiàn)源碼邏輯,盡可能以易懂的方式講解 MobX 源碼;
本系列文章:
《【用故事解讀 MobX源碼(一)】 autorun》
《【用故事解讀 MobX源碼(二)】 computed》
《【用故事解讀 MobX源碼(三)】 shouldCompute》
《【用故事解讀 MobX 源碼(四)】裝飾器 和 Enhancer》
《【用故事解讀 MobX 源碼(五)】 Observable》
文章編排:每篇文章分成兩大段,第一大段以簡(jiǎn)單的偵探系列故事的形式講解(所涉及人物、場(chǎng)景都以 MobX 中的概念為原型創(chuàng)建),第二大段則是相對(duì)于的源碼講解。
本文基于 MobX 3 源碼講解
=======================================
A. Story Time 1、 場(chǎng)景場(chǎng)景:
一位名為 張三 的銀行用戶賬戶情況為:
賬戶存款為 3(萬(wàn)元)
信用借貸為 2(萬(wàn)元)
你作為警署最高長(zhǎng)官,在一起金融犯罪中認(rèn)定 張三 為金融犯罪嫌疑犯,想自動(dòng)化跟蹤這位用戶的儲(chǔ)蓄情況,比如他一旦銀行存款有變更就打印出他當(dāng)前的賬戶存款
為了實(shí)現(xiàn)這個(gè)任務(wù),你下發(fā)命令給你的執(zhí)行官(MobX):
var bankUser = mobx.observable({ name: "張三", income: 3, debit: 2 }); mobx.autorun(() => { console.log("張三的賬戶存款:", bankUser.income); });
執(zhí)行官拿著這幾行代碼開(kāi)始部署警力來(lái)完成你下發(fā)的指令,并將這次行動(dòng)命名為 A計(jì)劃 (是不是很酷???)。你所要做的,就是等執(zhí)行官 MobX 執(zhí)行行動(dòng)部署完畢之后,坐在辦公室里一邊愜意地喝著咖啡,一邊在電腦上觀察張三賬戶的存款變化。
執(zhí)行官部署完畢后,首先會(huì)立即打印出 張三的賬戶存款: 3
后續(xù)張三的賬戶存款有更改的話,會(huì) 自動(dòng)執(zhí)行該部署方案,控制臺(tái)里就自動(dòng)打印其存款;
// 更改賬戶存款 bankUser.income = 4; bankUser.income = 10;
是不是很神奇很自動(dòng)化?
2、 部署方案作為警署最高長(zhǎng)官,你不必事必躬親過(guò)問(wèn)執(zhí)行官(MobX)部署的細(xì)節(jié),只要等著要結(jié)果就可以。
而作為執(zhí)行官(MobX),你得知道 A計(jì)劃 中部署方案的每一步細(xì)節(jié)。下面我們來(lái)一探究竟執(zhí)行官 MobX 到底是如何部署 A計(jì)劃 的。
2.1、 組織架構(gòu)執(zhí)行官(MobX) 擁有一套成熟的運(yùn)作機(jī)構(gòu)組織支撐任務(wù)的執(zhí)行。為了執(zhí)行這項(xiàng)任務(wù),涉及到 2 類職員和 1 個(gè)數(shù)據(jù)情報(bào)室:
觀察員:其工作職責(zé)是觀察并監(jiān)督嫌疑人特定信息,比如這里,監(jiān)視張三的收入(income)屬性,當(dāng)這項(xiàng)特征有變更的時(shí)候,及時(shí)向上級(jí)匯報(bào)(并執(zhí)行特定的操作);
探長(zhǎng):一方面負(fù)責(zé)管理劃歸給他的 觀察員,整合觀察員反饋的資訊;另一方面接受 MobX 執(zhí)行官交給他的任務(wù),在 適當(dāng)?shù)臅r(shí)機(jī) 執(zhí)行這項(xiàng)任務(wù)(此任務(wù)是打印張三的存款);
此外還會(huì)架設(shè)一個(gè) 數(shù)據(jù)情報(bào)室,方便執(zhí)行官 MobX、探長(zhǎng)和觀察員們 互相通過(guò)情報(bào)室進(jìn)行數(shù)據(jù)信息的交換。
具體組織架構(gòu)關(guān)系圖如下:
按照組織架構(gòu),執(zhí)行官 MobX 分解計(jì)劃細(xì)節(jié)并安排人員如下:
1.明確此次任務(wù)是 當(dāng)張三賬戶存款變更時(shí),打印其存款:
() => { console.log("張三的賬戶存款:", bankUser.income); }
2.將任務(wù)指派給執(zhí)行組中的探長(zhǎng) R1
3.派遣觀察組中的觀察員 O1 監(jiān)察張三賬戶的 bankUser.income 屬性
4.探長(zhǎng) R1 任務(wù)中所需的“張三的賬戶存款” 數(shù)值必須從觀察員 O1 那兒獲??;
5.同時(shí)架設(shè)數(shù)據(jù)情報(bào)室,方便信息交換;
人員安排完畢,執(zhí)行官拿出一份 部署方案書,一聲令下 “各位就按這套方案執(zhí)行任務(wù)吧!”;
在部署方案中下達(dá)之后,機(jī)構(gòu)各組成員各司其職,開(kāi)始有條不紊地開(kāi)始運(yùn)作,具體操作時(shí)序圖如下所示:
對(duì)時(shí)序圖中的關(guān)鍵點(diǎn)做一下解釋:
執(zhí)行官 MobX 先將探長(zhǎng) R1 信息注冊(cè)到中心情報(bào)室;(有些情況下,比如偵破大案要案時(shí)需要多位探長(zhǎng)協(xié)作,將存在多位探長(zhǎng)同時(shí)待命的情況;當(dāng)然此次任務(wù)中,只有探長(zhǎng) R1 在待命)
中心情報(bào)室給執(zhí)行官 MobX 返回所有待命探長(zhǎng)列表(此例中僅有探長(zhǎng) R1);
執(zhí)行官 MobX 挨個(gè)讓每位待命探長(zhǎng)按以下步驟操作:
3.1. 探長(zhǎng)出發(fā)做任務(wù)時(shí),記得給中心情報(bào)室通告一聲,好比上班“打卡”操作。(記錄事務(wù)序號(hào),好讓中心情報(bào)室知曉案情復(fù)雜度;有些案件中將有很多探長(zhǎng)同時(shí)執(zhí)行任務(wù))
3.2 探長(zhǎng) R1 開(kāi)始監(jiān)督并執(zhí)行 MobX 交給他的任務(wù)(“打印張三的存款”)
3.3 首先在數(shù)據(jù)情報(bào)室中“注冊(cè)”,將自己設(shè)置成 正在執(zhí)勤人員;(這一步很重要)
3.4 隨后真正進(jìn)入執(zhí)行任務(wù)的狀態(tài)
3.5 在執(zhí)行任務(wù)的時(shí)候,發(fā)現(xiàn)需要張三的存款(income)這個(gè)數(shù)值,可這個(gè)數(shù)值探長(zhǎng) R1 不能直接獲取,必須通過(guò)觀察員 O1 取得,于是通過(guò)專用通訊機(jī)和觀察員 O1 取得聯(lián)系,請(qǐng)求獲取要張三的存款(income)
3.6 觀察員 O1 發(fā)現(xiàn)有人通過(guò)專用通訊機(jī)請(qǐng)求張三的存款(income),就開(kāi)始如下操作:
3.6.1 將自己的信息 經(jīng)過(guò) 數(shù)據(jù)情報(bào)室,然后傳達(dá)給請(qǐng)求方;只有上級(jí)(不一定是探長(zhǎng),有可能是其他的上級(jí)領(lǐng)導(dǎo))才能通過(guò)這個(gè)專用通訊機(jī)發(fā)消息給觀察員;
3.6.2 數(shù)據(jù)情報(bào)室 將該信息同步給 正在執(zhí)勤人員 —— 即探長(zhǎng) R1
3.6.3 同時(shí)將張三的存款(income)返回給請(qǐng)求方;(該消息不用經(jīng)過(guò) 數(shù)據(jù)情報(bào)室)
3.7 此時(shí)探長(zhǎng)擁有兩份信息:任務(wù)所需要的張三的存款(income),以及觀察員 O1 的相關(guān)信息;因時(shí)間緊,執(zhí)行任務(wù)優(yōu)先級(jí)高,探長(zhǎng) R1 先拿著張三的存款(income)數(shù)據(jù),先做完任務(wù)。(至于觀察員 O1 的信息 先臨時(shí)保存 ,方便后續(xù)處理);
3.8 等到任務(wù)執(zhí)行完了,探長(zhǎng)松了一口氣,匯總并整理臨時(shí)保存的觀察員信息;在這一階段,探長(zhǎng) R1 才和 觀察員 O1 互相建立牢固的關(guān)系(可以理解為,互留聯(lián)系方式,可隨時(shí)聯(lián)系得上對(duì)方),也是在這個(gè)時(shí)候,觀察員 O1 才知曉其上級(jí)領(lǐng)導(dǎo)是探長(zhǎng) 01;
3.9 此后,探長(zhǎng) R1 發(fā)消息告知中心情報(bào)室,削去此次事務(wù)(說(shuō)明事務(wù)執(zhí)行完了),好比下班 “打卡”操作。
至此 A 計(jì)劃部署完畢
上述時(shí)序圖中有些地方需要強(qiáng)調(diào)一下:
張三的存款(income)只歸觀察員 O1 監(jiān)視,探長(zhǎng) R1 所獲取的張三的存款只能通過(guò)觀察員 O1 間接獲取到,探長(zhǎng)不能越權(quán)去直接獲?。?/p>
探長(zhǎng) R1 和觀察員 O1 建立關(guān)系并非一步到位,是 分兩個(gè)階段 的:
第一階段(對(duì)應(yīng)上述步驟中 3.6.2)是在執(zhí)行任務(wù)期間,僅僅是建立短暫的 單向關(guān)系;即,此時(shí)探長(zhǎng) R1 知曉觀察員 O1 的情況,但反過(guò)來(lái),但觀察員 O1 并不知曉探長(zhǎng) R1 ;
第二階段(對(duì)應(yīng)上述步驟中 3.8)在任務(wù)執(zhí)行完,收尾階段的時(shí)候,探長(zhǎng)才有時(shí)間梳理、整合任務(wù)期間的探員信息(因?yàn)槿蝿?wù)中涉及到有可能多個(gè)觀察員,當(dāng)然此次任務(wù)中只有 1 個(gè)),那時(shí)候才有時(shí)間 慢慢地 和各位探員互相交換信息,建立 明確且牢固 的關(guān)系;
2.3、 任務(wù)執(zhí)行自動(dòng)化作為警署最高長(zhǎng)官的你,拿著這份部署方案,眉頭緊鎖:“我說(shuō)執(zhí)行官 ,就為了區(qū)區(qū)獲取張三的存款這么件事兒,耗費(fèi)那么多人力資源,值么?直接獲取 bankUser.income 不就行了?!”
“emm...,這所做的努力,圖的是普適性和 自動(dòng)化響應(yīng)。”執(zhí)行官 MobX 淡然自如,不緊不慢徐徐道來(lái),“有了上面那套機(jī)制,一方面每當(dāng)張三的存款變更后,就會(huì) 自動(dòng)化執(zhí)行上述部署方案的過(guò)程;另一方面很方便擴(kuò)展,后續(xù)針對(duì)其他監(jiān)察,只需要在此部署方案中稍加改動(dòng)就可以,所需要的高級(jí)功能都是基于這個(gè)方案做延伸。真正做到了 ’部署一次,全自動(dòng)化執(zhí)行‘ 的目的?!?/p>
隨后,執(zhí)行官 MobX 給出一張當(dāng)張三存款發(fā)生變化之時(shí),此機(jī)構(gòu)的運(yùn)作時(shí)序圖;
"的確,小機(jī)構(gòu)靠人力運(yùn)作,大機(jī)構(gòu)才靠制度運(yùn)轉(zhuǎn)。那就先試運(yùn)行這份部署計(jì)劃,看它能否經(jīng)受得起時(shí)間的考驗(yàn)吧。" 警署最高長(zhǎng)官拍拍執(zhí)行官 MobX 的肩膀,若有所思地踱步出了辦公室。
(此節(jié)完。未完待續(xù))
B. Source Code Time上面講那么久的故事,是為了給講源碼做鋪墊。
接下來(lái)將會(huì)貼 MobX 源碼相關(guān)的代碼,稍顯枯燥,我只能盡量用通俗易懂的話來(lái)分析講解。
先羅列本文故事中人物與 MobX 源碼概念映射關(guān)系:
故事人物 | MobX 源碼 | 解釋 |
---|---|---|
警署最高長(zhǎng)官 | (無(wú)) | MobX 用戶,沒(méi)錯(cuò),就是你 |
執(zhí)行官 MobX | MobX | 整個(gè) MobX 運(yùn)行環(huán)境 |
A計(jì)劃 | autorun | 官方文檔 -mobx.autorun方法 |
探長(zhǎng) | reaction | 官方文檔 - Reaction 響應(yīng)對(duì)象 |
觀察員 | observable | 官方文檔 - Observable 對(duì)象 |
數(shù)據(jù)情報(bào)室 | globalstate | MobX 運(yùn)行環(huán)境中的 ”全局變量“,不同對(duì)象通過(guò)它進(jìn)行數(shù)據(jù)傳遞通信,十分重要;(但這其實(shí)在一定程度上破壞了內(nèi)聚性,給源碼閱讀、程序 debug 造成一定的難度) |
本文的重點(diǎn)是講解 A 計(jì)劃所對(duì)應(yīng)的 autorun 的源碼,先從整體上對(duì) MobX 的運(yùn)行有個(gè)大致了解,而所涉及到的 Reaction、Observable 等細(xì)節(jié)概念后續(xù)章節(jié)再做展開(kāi),這里僅僅大致提及其部分功能和屬性;
1、下達(dá)的命令回到故事的最開(kāi)始,你給 MobX 下達(dá)的命令如下:
var bankUser = mobx.observable({ name: "張三", income: 3, debit: 2 }); mobx.autorun(() => { console.log("張三的賬戶存款:", bankUser.income); });
只有兩條語(yǔ)句,第一條語(yǔ)句是創(chuàng)建觀察員,第二條語(yǔ)句是執(zhí)行 A 計(jì)劃(內(nèi)含委派探長(zhǎng)、架設(shè)情報(bào)局等工作)
我們挨個(gè)細(xì)分講解。
1.1、第一條語(yǔ)句:創(chuàng)建觀察員 - Observable第一條語(yǔ)句:
const bankUser = mobx.observable({ name: "張三", income: 3, debit: 2 })
我們調(diào)用 mobx.observable 的時(shí)候,就創(chuàng)建了 Observable 對(duì)象,對(duì)象的所有屬性都將被拷貝至一個(gè)克隆對(duì)象并將克隆對(duì)象轉(zhuǎn)變成可觀察的。
因此這一行代碼執(zhí)行后, name、income 和 debit 這三個(gè)屬性都變成可觀察的;
若以故事場(chǎng)景來(lái)敘述中,執(zhí)行官 MobX 在部署的時(shí)候委派了 3 位探員,分別監(jiān)視這 3 個(gè)屬性;而故事中交給探長(zhǎng)任務(wù)中僅僅涉及了那位監(jiān)視 income 屬性的觀察員 O1;(所以另外兩位探員都還在休息)
在這里可以看到 惰性求值 的思想的應(yīng)用,只有在 必要的時(shí)候 啟用 所觀察對(duì)象,粒度細(xì),有利于性能提升;
之所以只有 1 位觀察員,是因?yàn)橛捎谏霞?jí)下達(dá)的具體任務(wù)內(nèi)容是:
() => { console.log("張三的賬戶存款:", bankUser.income); }
看看任務(wù)中出現(xiàn) bankUser.income 而并沒(méi)有出現(xiàn) bankUser.debit 和 bankUser.name,說(shuō)明這個(gè)任務(wù)只 牽連 探員O1,而和其他探員無(wú)關(guān)。
注:本文暫時(shí)先不分析 mobx.observable 的源碼,留待后續(xù)專門的一章來(lái)分析;迫不及待的讀者,可以先閱讀網(wǎng)上其他源碼文章,比如:Mobx 源碼解讀(二) Observable
觀察員有兩個(gè)非常重要的行為特征:
當(dāng)有人請(qǐng)求觀察員所監(jiān)控的值(比如income)時(shí),會(huì)觸發(fā) MobX 所提供的 reportObserved 方法;
當(dāng)觀察員所監(jiān)控的值(比如income)發(fā)生變化時(shí),會(huì)觸發(fā) MobX 所提供的 propagateChanged 方法;
這里留一個(gè)印象,本文后續(xù)在適當(dāng)?shù)臅r(shí)機(jī)再講解這兩個(gè)方法是在什么時(shí)候觸發(fā)的;
1.2、第二條語(yǔ)句:A 計(jì)劃的實(shí)施 - autorun第二條語(yǔ)句:
mobx.autorun(() => { console.log("張三的賬戶存款:", bankUser.income); });
這里出現(xiàn)的 MobX 中的 mobx.autorun 方法對(duì)應(yīng)故事中的 整個(gè)A計(jì)劃的實(shí)施:
autorun 的直觀含義就是 響應(yīng)式函數(shù) —— 響應(yīng)觀察值的變化而自動(dòng)執(zhí)行指定的函數(shù)。
我們看一下其源碼:
附源碼位置:autorun
從這里可以看出 autorun 大致的脈絡(luò)如下:
① 首先創(chuàng)建 Reaction 類型對(duì)象。new Reaction 操作可以理解為創(chuàng)建探長(zhǎng) R1 ;
探長(zhǎng)對(duì)應(yīng)的類是 Reaction,其關(guān)鍵特征是 監(jiān)督并控制任務(wù)的執(zhí)行;
本文的下一節(jié)將詳細(xì)介紹探長(zhǎng)們的 "生活日常",此處先放一放。
② 其次分配任務(wù)。源碼中所涉及到的 view() 方法 就是具體的任務(wù)內(nèi)容,即上述故事中的 打印張三賬戶存款 這項(xiàng)任務(wù):
() => { console.log("張三的賬戶存款:", bankUser.income); }
③ 最后,立即執(zhí)行一次部署方案。
代碼中的 reaction.schedule() 表示讓探長(zhǎng) R1 立即執(zhí)行執(zhí)行一次部署任務(wù),執(zhí)行的結(jié)果是完成人員部署,并讓探長(zhǎng) R1 打印了一次張三賬戶存款;(同時(shí)和觀察員 O1 建立關(guān)系)
現(xiàn)在你應(yīng)該會(huì)理解官方文檔中的那句 ”使用 autorun 時(shí),所提供的函數(shù)總是立即被觸發(fā)一次“ 話了。
看一下 schedule 方法:
看上去很簡(jiǎn)單,不到 5 行代碼做了兩件事情:
① 將探長(zhǎng)入列;
② 讓隊(duì)列中的 所有探長(zhǎng)(當(dāng)然,在我們的示例中僅僅只有 1 名探長(zhǎng))都執(zhí)行 runReaction 方法
對(duì)應(yīng)時(shí)序圖中所標(biāo)注的 1、2 兩部分:
所謂的 部署(schedule) 就是敦促 各位探長(zhǎng)執(zhí)行 runReaction 方法。
第二條語(yǔ)句從整體上看就這樣了。
接下來(lái)就讓我們來(lái)詳細(xì)分析探長(zhǎng)的 runReaction 的方法,在該方法中 探長(zhǎng)將聯(lián)動(dòng)觀察員、數(shù)據(jù)情報(bào)室一起在部署方案中發(fā)揮監(jiān)督、自動(dòng)化響應(yīng)功能。
2、每位探長(zhǎng)的生活日常任務(wù)的執(zhí)行全靠探長(zhǎng),不過(guò)探長(zhǎng)的存在常常是 依賴觀察員 的,這是因?yàn)樵谌蝿?wù)過(guò)程中,如果想要獲取所監(jiān)視的張三的存款(income),必須通過(guò)觀察員獲取,自身是沒(méi)有權(quán)力繞過(guò)觀察員直接獲取的哦。
每位探長(zhǎng)的任務(wù)執(zhí)行流大致如下:
主流程大致只有 4 步:
① 開(kāi)始執(zhí)行(runReaction)
② 判斷是否執(zhí)行(shouldCompute)
③ 執(zhí)行任務(wù)(onInvalidate)
④ 結(jié)束
這些基就是每位探長(zhǎng)的生活的總體了。下面我們挑其中的第 ① 、 ③ 步來(lái)講解。
其實(shí)圖中另外有一個(gè)很重要的 shouldCompute 判斷方法步驟,根據(jù)這個(gè)方法探長(zhǎng)可以自行判斷 是否執(zhí)行任務(wù),并非所有的任務(wù)都需要執(zhí)行,這一步的作用是優(yōu)化 MobX 執(zhí)行效率。該方法源碼內(nèi)容先略過(guò),后續(xù)章節(jié)再展開(kāi)。2.1、開(kāi)始執(zhí)行 - runReaction
該函數(shù)比較簡(jiǎn)單,主要是為執(zhí)行任務(wù) ”做一些準(zhǔn)備“,給任務(wù)營(yíng)造氛圍。用 startBatch() 開(kāi)頭,用 endBatch() 結(jié)尾,中間隔著 onInvalidate。
startBatch() 和 endBatch() 這兩個(gè)方法一定是成對(duì)出現(xiàn),用于影響 globalState 的 inBatch 屬性,表明開(kāi)啟/關(guān)閉 一層新的事務(wù),可以理解為 上下班打卡 操作。
只不過(guò) startBatch() 是 ”上班打卡“,對(duì)應(yīng)時(shí)序圖(3.1) 部分:
endBatch() 相當(dāng)于 “下班打卡”,不過(guò)稍微復(fù)雜一些,包含一些 收尾 操作,對(duì)應(yīng)時(shí)序圖(3.9)部分:
我們繼續(xù)看隔在中間的 onInvalidate 方法。?
2.2、執(zhí)行任務(wù) - onInvalidate此階段是流程中最重要的階段。
你翻看源碼,將會(huì)發(fā)現(xiàn)此方法 onInvalidate 是 Reaction 類的一個(gè)屬性,且在初始化 Reaction 時(shí)傳入到構(gòu)造函數(shù)中的,這樣做的目的是方便做擴(kuò)展。
所以,autorun 方法本質(zhì)就是一種預(yù)定義好的 Reaction —— 你可以依葫蘆畫瓢,將自定義 onInvalidate 方法傳給 Reaction 來(lái)實(shí)現(xiàn)自己的 計(jì)劃任務(wù)(什么 Z計(jì)劃啊、阿波羅計(jì)劃啊,名字都起好了,就差實(shí)現(xiàn)了!!....);
回過(guò)頭來(lái),在剛才所述的 autorun 源碼中找到 Reaction 類初始化部分:
const reaction = new Reaction(name, function() { this.track(reactionRunner) })
可以看到 onInvalidate 方法就是:
function() { this.track(reactionRunner) }
這就不難理解 onInvalidate 實(shí)際執(zhí)行的是 reaction.track 方法。
繼續(xù)跟蹤源碼,會(huì)發(fā)現(xiàn)該 onInvalidate 階段主要是由 3 個(gè)很重要的子流程所構(gòu)成:
3.1 跟蹤任務(wù)(track)
3.2 執(zhí)行任務(wù)(trackDerivedFunction)
3.3 更新依賴(bindDependencies)
這 3 個(gè)函數(shù)并非是并行關(guān)系,而是嵌套關(guān)系,后者是嵌套在前者內(nèi)執(zhí)行的:
題外話:是不是很像 Koa 的 洋蔥圈模型 ??2.2.1、track
track 方法內(nèi)容也簡(jiǎn)單,和剛才所說(shuō)的 runReaction 方法類似 —— 也是用 startBatch() 開(kāi)頭,用 endBatch() 結(jié)尾,中間隔著 trackDerivedFunction。
所以在這個(gè)案例中,整個(gè)部署階段是執(zhí)行 兩次 startBatch() 和 endBatch() 的;在往后復(fù)雜的操作中,執(zhí)行的次數(shù)有可能更多。
我們都知道數(shù)據(jù)庫(kù)中的事務(wù)概念,其表示一組原子性的操作。Mobx 則借鑒了 事務(wù) 這個(gè)概念,它實(shí)現(xiàn)比較簡(jiǎn)單,就是通過(guò) 成對(duì) 使用 startBatch 和 endBatch 來(lái)開(kāi)始和結(jié)束一個(gè)事務(wù),用于批量處理 Reaction 的執(zhí)行,避免不必要的重新計(jì)算。
因此到目前這一步,MobX 程序正處在 第二層 事務(wù)中。
MobX 暴露了 transaction 這一底層 API 供用戶調(diào)用,讓用戶能夠?qū)崿F(xiàn)一些較為高級(jí)的應(yīng)用,具體可參考 官方文檔 - Transaction(事務(wù)) 章節(jié)獲取更多信息。
接下來(lái)繼續(xù)看隔在中間的 trackDerivedFunction 方法。?
2.2.2、trackDerivedFunction我們總算到了探長(zhǎng) 真正執(zhí)行任務(wù) 的步驟了,之前講的所有流程都是為了這個(gè)函數(shù)服務(wù)的。
該環(huán)節(jié)的第 1 條語(yǔ)句:
globalState.trackingDerivation = derivation;
對(duì)應(yīng)時(shí)序圖(3.3):
作用是將 derivation (此處等同于 reaction 對(duì)象)掛載到 ”全局變量“ globalState 的 trackingDerivation 屬性上,這樣其他對(duì)象就能獲取到該 derivation 對(duì)象的數(shù)據(jù)了。這好比將探長(zhǎng)在數(shù)據(jù)情報(bào)室中注冊(cè)為 正在執(zhí)勤人員,后續(xù)觀察員 O1 會(huì)向數(shù)據(jù)情報(bào)室索取 正在執(zhí)勤人員 人,然后將自身信息輸送給他 —— 從結(jié)果上看,就相當(dāng)于 觀察員 O1 直接和 探長(zhǎng) R1 匯報(bào);(之所以要經(jīng)由數(shù)據(jù)情報(bào)室,是因?yàn)樵趫?zhí)行任務(wù)時(shí)候,有可能其他工種的人也需要 正在執(zhí)勤人員 的信息)
該環(huán)節(jié)的第 2 條語(yǔ)句:
result = f.call(context); // 效果等同于 result = console.log("張三的賬戶存款:", bankUser.income);
對(duì)應(yīng)時(shí)序圖(3.4):
沒(méi)錯(cuò),就是本次部署的 終極目的 —— 打印張三賬戶存款!
MobX 將真正的目的執(zhí)行之前里三層外三層地包裹其他操作,是為了將任務(wù)的運(yùn)行情況控制在自己營(yíng)造的環(huán)境氛圍中。為什么這么做呢?
這么做是基于一個(gè)前提,該前提是:所運(yùn)行的任務(wù) MobX 它無(wú)法控制(警署長(zhǎng)官今天下達(dá) A 命令,明天下達(dá) B 命令,控制不了)。
所以 MobX 就將任務(wù)的執(zhí)行籠罩在自己所營(yíng)造的氛圍中,改變不了任務(wù)實(shí)體,我改變環(huán)境總行了吧???!
由于環(huán)境是自己營(yíng)造的,MobX 可以為所欲為,在環(huán)境中穿插各種因素:探長(zhǎng)、觀察員、數(shù)據(jù)情報(bào)室等等(后續(xù)還有其他角色),這樣就將任務(wù)的運(yùn)行盡最大可能地控制在這套所創(chuàng)造的體系中 —— 孫猴子不也翻不出如來(lái)佛的五指山么?
雖然更改不了任務(wù)內(nèi)容,不過(guò) MobX 實(shí)際在任務(wù)中安插觀察員 O1 了,所以呢,當(dāng)探長(zhǎng)在執(zhí)行任務(wù)時(shí),將觸發(fā)時(shí)序圖中 (3.5)(3.6)兩步反應(yīng):
復(fù)雜么?也還好,(3.6)是由 (3.5)觸發(fā)的,(3.5)對(duì)應(yīng)的操作是:探長(zhǎng) R1 想要獲取的張三 income 屬性。
(所以,劃重點(diǎn),敲黑板!! 如果任務(wù)中不涉及到 income 這項(xiàng)屬性,那么就不會(huì)有 (3.5)的操作,也就沒(méi)有 (3.6)什么事)
由于探長(zhǎng) R1 所執(zhí)行的任務(wù)中用到 bankUser.income 變量,這里的 . 符號(hào)其實(shí)就是 get() 操作;一旦涉及到 get() 操作,監(jiān)督這個(gè) income 屬性的觀察員 O1 就會(huì)執(zhí)行 reportObserved 方法。 該 reportObserved 方法對(duì)應(yīng)的源碼如下:
那么多行代碼,我們主要關(guān)注其中操作影響到探長(zhǎng)(derivation)中的操作:
更新探長(zhǎng)的 lastAccessedBy 屬性(事務(wù) id),這個(gè)是為了避免重復(fù)操作而設(shè)置的
更新探長(zhǎng)的 newObserving 屬性,將探員信息推入到該隊(duì)列中(對(duì)應(yīng)時(shí)序圖 (3.6.2)操作),這個(gè)比較重要,后續(xù)探長(zhǎng)和觀察員更新依賴關(guān)系就靠這個(gè)屬性了;
隨后,任務(wù)執(zhí)行完(時(shí)序圖(3.7))后,探長(zhǎng)就開(kāi)始著手更新和觀察員 O1 的關(guān)聯(lián)關(guān)系了。?
2.2.3、bindDependencies探長(zhǎng) R1 整理和觀察員的關(guān)系是在時(shí)序圖 (3.8)處:
兩者依賴更新的算法在參考文章Mobx 源碼解讀(四) Reaction 中有詳細(xì)的注解,推薦閱讀。這里也做一下簡(jiǎn)單介紹。
該函數(shù)的目的,是用 derivation.newObserving 去更新 derivation.observing 屬性:
derivation.newObserving 就是剛才在所述時(shí)序圖 (3.6.2)操作是生成的
執(zhí)行完之后 derivation.newObserving 會(huì)置空,而 derivation.observing 屬性獲得更新,該屬性反映的 探長(zhǎng) 和 觀察員 之間最新的關(guān)聯(lián)關(guān)系;
依賴更新肯定需要遍歷,由于涉及到探長(zhǎng)、觀察員兩個(gè)維度的數(shù)組,樸素算法的時(shí)間復(fù)雜度將是 O(n^2),而 MobX 中使用 3 次遍歷 + diffValue 屬性的輔助將復(fù)雜度降到了 O(n)。? ?
下面我用示例來(lái)展現(xiàn)這 3 次遍歷過(guò)程。
2.2.3.1、先看一下整體的 input / output
假設(shè)在執(zhí)行 bindDependencies 函數(shù)之前, derivation.observing 已有 2 個(gè)元素,derivation.newObserving 有 5 個(gè)對(duì)象(由于 A、B 各重復(fù)一次,實(shí)際只有 3 個(gè)不同的對(duì)象 A、B、C),經(jīng)過(guò) bindDependencies 函數(shù)后 derivation.observing 將獲得更新,如下所示:
2.2.3.2、第一次循環(huán):newObserving 數(shù)組去重
第一次循環(huán)遍歷 newObserving,利用 diffValue 進(jìn)行去重,一次遍歷就完成了(這種 數(shù)組去重算法 可以添加到面試題庫(kù)中了??)。注意其中 diffValue 改變情況:
由于 A 對(duì)象(引用)既在 observing 數(shù)組也在 newObserving 數(shù)組中,當(dāng)改變 newObserving 中 A 元素的 diffValue 值的時(shí)候,observing 數(shù)組 A 屬性也自然跟著改變;
這次遍歷后,所有 最新的依賴 的 diffValue 值都是 1 了哦,而且去除了所有重復(fù)的依賴。
2.2.3.3、第二次循環(huán):去除observing 數(shù)組陳舊關(guān)聯(lián)
接下去第二次遍歷針對(duì) observing 數(shù)組,做了兩件事:
如果對(duì)象的 diffValue 值為 0 (為 0 說(shuō)明不在 newObserving 數(shù)組中,是陳舊的關(guān)聯(lián)),則調(diào)用 removeObserver 去除該關(guān)聯(lián);因此這次遍歷之后會(huì)刪除 observing 數(shù)組中 D 對(duì)象
讓 observing 數(shù)組中剩余對(duì)象的 diffValue 值變成 0;
這一次遍歷之后,去除了所有陳舊的依賴,且遺留下來(lái)的對(duì)象的 diffValue 值都是 0 了。
2.2.3.4、第三次循環(huán):將新增依賴添加到 observing
第二次遍歷針對(duì) newObserving 數(shù)組,做了一件事:
如果 diffValue 為 1,說(shuō)明是新增的依賴,調(diào)用 addObserver 新增依賴,并將 diffValue 置為 0
這最后一次遍歷,observing 數(shù)組關(guān)聯(lián)都是最新,且 diffValue 都是 0 ,為下一次的 bindDependencies 做好了準(zhǔn)備。
至此,A計(jì)劃部署方案(autorun 源碼)就講完了。 A 計(jì)劃執(zhí)行后,探長(zhǎng) R1 完成上級(jí)下達(dá)的任務(wù),同時(shí)也和觀察員 O1 建立起明確且牢固的依賴。
3、響應(yīng)觀察值的變化 - propagateChanged一旦張三存款發(fā)生變化,那么一定會(huì)被觀察員 O1 監(jiān)視到,請(qǐng)問(wèn)此時(shí)觀察員會(huì)怎么做?
或許有人會(huì)說(shuō),觀察員 O1 然后上報(bào)給探長(zhǎng) R1 ,然后讓探長(zhǎng) R1 再執(zhí)行一次打印任務(wù);
從最終結(jié)果角度去理解,上面的陳述其實(shí)沒(méi)毛病,的確是觀察員 O1 驅(qū)動(dòng)探長(zhǎng) R1 再打印一次;
但若從執(zhí)行過(guò)程角度去看,以上陳述是 錯(cuò)誤的! ?
觀察員 O1 監(jiān)視到變化之后,的確通知探長(zhǎng) R1了,但探長(zhǎng)并非直接執(zhí)行任務(wù),而是通知 MobX 再按照 A 計(jì)劃部署方案執(zhí)行一遍!;(不得不感慨,這是多么死板地執(zhí)行機(jī)制)
源碼中是怎么體現(xiàn)的呢?
上面提及到過(guò),當(dāng)觀察員所監(jiān)控的值(比如income)發(fā)生變化時(shí),會(huì)觸發(fā) MobX 所提供的 propagateChanged 方法。
propagateChanged 對(duì)應(yīng)的源碼如下:
代碼很簡(jiǎn)單,即遍歷觀察員的上級(jí)們,讓他們調(diào)用 onBecomeStale() 方法 。該觀察員有可能不止對(duì)一位上級(jí)(上級(jí)也不一定只有探長(zhǎng))負(fù)責(zé),每位上級(jí)的 onBecomeStale() 是不一樣的。(當(dāng)然,此故事中觀察員 O1 只有 1 位上級(jí) —— 探長(zhǎng) R1)
我們看一下探長(zhǎng)這類上級(jí)所定義的 onBecomeStale:
onBecomeStale() { this.schedule() }
簡(jiǎn)單明了,就是直接 再一次執(zhí)行部署方案。如此簡(jiǎn)單樸素,真正做到了 “一視同仁” —— 無(wú)論什么任務(wù),一旦部署就緒,任何觀察員反饋情況有變(比如張三賬戶余額發(fā)生變動(dòng)了),探長(zhǎng)都是讓 MobX 重新執(zhí)行一遍部署方案,并不會(huì)直接執(zhí)行任務(wù),反正部署方案中有探長(zhǎng)執(zhí)行任務(wù)的步驟嘛。??
所謂的流程化、設(shè)計(jì)模式,都多多少少在一定程度上約束個(gè)體行為(喪失了一部分靈活性),而取得整體上的普適性和可擴(kuò)展性。
現(xiàn)在再回過(guò)頭來(lái)看剛才官方文檔截圖中的第二句話:"然后每次它的依賴關(guān)系改變時(shí)會(huì)再次被觸發(fā)"
它所表達(dá)的意思其實(shí)就是:當(dāng)張三余額發(fā)生變化的時(shí)候,將 自動(dòng)觸發(fā) 上述的 A 計(jì)劃部署方案。
4、小測(cè)試問(wèn):下列代碼中 message.title = "Hello world" 為何不會(huì)觸發(fā) autorun 再次執(zhí)行?
const message = observable({ title: "hello" }) autorun(() => { console.log(message) }) // 不會(huì)觸發(fā)重新運(yùn)行 message.title = "Hello world"
其實(shí)上述問(wèn)題來(lái)自官方的一個(gè)問(wèn)題,若無(wú)思路的,可以先參考官方文檔 常見(jiàn)陷阱: console.log。如果能從源碼角度回答這個(gè)問(wèn)題,則說(shuō)明已經(jīng)理解本節(jié)所講的 autorun 的知識(shí)點(diǎn)了
5、小結(jié)此篇是開(kāi)篇,所闡述的概念僅僅占 MobX 體系冰山一角。
故事中還還有很多問(wèn)題,比如:
如何成為一名合格的探員、觀察員?(用程序員的話講,就是有哪些屬性和方法)
數(shù)據(jù)情報(bào)室到底還存有哪些關(guān)鍵信息?
組織機(jī)構(gòu)中是否還有其他組、成員?
多個(gè)探長(zhǎng)、觀察員情況下,這套部署方案又是如何的呢?
....
以上問(wèn)題的答案,讀者可能已經(jīng)知道,那些還不知道的可以自己留作自己思考;在后續(xù)章節(jié),我也會(huì)在適當(dāng)?shù)臅r(shí)機(jī)解答上述中的問(wèn)題;
(也歡迎大家提問(wèn),將有可能采納編入后續(xù)的故事敘述中來(lái)解答)
后續(xù)的章節(jié)中,將繼續(xù)介紹 ComputedValue、Action、Atom、Derivation、Spy 等,正是這些功能角色使得 MobX 有著強(qiáng)大的自動(dòng)化能力,合理運(yùn)用了惰性求值、函數(shù)式編程等編程范式,使 MobX 在復(fù)雜交互應(yīng)用中大放異彩;
參考文章羅列本文所參考的文章,感謝他們所給予的幫助:
官方中文文檔:不多講,官方文檔最好翻一遍。
awesome-mobx: MobX 相關(guān)資源整合,方便多看多練。
Mobx 源碼解讀(一) 基本概念:優(yōu)質(zhì)的 MobX 源碼解讀文章,受益匪淺。
MobX 核心源碼解析:本文深入 MobX 源碼來(lái)解析其核心原理以及工作流程,推薦閱讀;
探秘 MobX:本文短小精悍,主講observable 和 autorun 原理
MobX 原理:本文對(duì) deviration 著墨較多
下面的是我的公眾號(hào)二維碼圖片,歡迎關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93372.html
摘要:場(chǎng)景為了多維度掌控嫌疑犯的犯罪特征數(shù)據(jù),你警署最高長(zhǎng)官想要獲取并實(shí)時(shí)監(jiān)控張三的貸款數(shù)額存貸比存款和貸款兩者比率的變化。 ================前言=================== 初衷:以系列故事的方式展現(xiàn) MobX 源碼邏輯,盡可能以易懂的方式講解源碼; 本系列文章: 《【用故事解讀 MobX源碼(一)】 autorun》 《【用故事解讀 MobX源碼(二)】...
摘要:最簡(jiǎn)單的情況張三的存貸這里我們創(chuàng)建了實(shí)例探長(zhǎng)實(shí)例觀察員這個(gè)示例和我們之前在首篇文章用故事解讀源碼一中所用示例是一致的。 ================前言=================== 初衷:以系列故事的方式展現(xiàn) MobX 源碼邏輯,盡可能以易懂的方式講解源碼; 本系列文章: 《【用故事解讀 MobX源碼(一)】 autorun》 《【用故事解讀 MobX源碼(二)】...
摘要:前言初衷以系列故事的方式展現(xiàn)源碼邏輯,盡可能以易懂的方式講解源碼本系列文章用故事解讀源碼一用故事解讀源碼二用故事解讀源碼三用故事解讀源碼四裝飾器和用故事解讀源碼五文章編排每篇文章分成兩大段,第一大段以簡(jiǎn)單的偵探系列故事的形式講解所涉及人物場(chǎng) ================前言=================== 初衷:以系列故事的方式展現(xiàn) MobX 源碼邏輯,盡可能以易懂的方式...
摘要:所以這是一篇插隊(duì)的文章,用于去理解中的裝飾器和概念。因此,該的作用就是根據(jù)入?yún)⒎祷鼐唧w的描述符。其次局部來(lái)看,裝飾器具體應(yīng)用表達(dá)式是,其函數(shù)簽名和是一模一樣。等裝飾器語(yǔ)法,是和直接使用是等效等價(jià)的。 ================前言=================== 初衷:以系列故事的方式展現(xiàn) MobX 源碼邏輯,盡可能以易懂的方式講解源碼; 本系列文章: 《【用故事解...
摘要:一其實(shí)是一個(gè)比較輕便的可擴(kuò)展的狀態(tài)管理工具,是一個(gè)由以及一些其他團(tuán)隊(duì)的人共同維護(hù)的開(kāi)源項(xiàng)目。當(dāng)應(yīng)用公共狀態(tài)的組件在狀態(tài)發(fā)生變化的時(shí)候,會(huì)自動(dòng)完成與狀態(tài)相關(guān)的所有事情,例如自動(dòng)更新自動(dòng)緩存數(shù)據(jù),自動(dòng)通知等。 一、MobX MobX其實(shí)是一個(gè)比較輕便的可擴(kuò)展的狀態(tài)管理工具,是一個(gè)由Facebook以及一些其他團(tuán)隊(duì)的人共同維護(hù)的開(kāi)源項(xiàng)目。 當(dāng)應(yīng)用公共狀態(tài)的組件在狀態(tài)發(fā)生變化的時(shí)候,會(huì)自動(dòng)完...
閱讀 3758·2021-08-11 11:16
閱讀 1629·2019-08-30 15:44
閱讀 1998·2019-08-29 18:45
閱讀 2279·2019-08-26 18:18
閱讀 1010·2019-08-26 13:37
閱讀 1576·2019-08-26 11:43
閱讀 2125·2019-08-26 11:34
閱讀 380·2019-08-26 10:59