================前言===================html
本系列文章:react
=======================================git
最高警長看完執行官(MobX)的自動部署方案,對 「觀察員」 這個基層人員工做比較感興趣,自執行官拿給他部署方案的時候,他就注意到全部上層人員的功能都是基於該底層人員高效的工做機制;github
次日,他找上執行官(MobX)一塊兒去視察「觀察員」所在機構部門(下面簡稱爲 」觀察局「),想更深刻地瞭解 「觀察員」 運行分配機制。算法
當最高警長到達部門的時候,剛好遇到該部門剛好要開始執行 MobX 前不久新下發的任務,要求監控 parent
對象的一舉一動:編程
var parent = { child: { name: 'tony', age: 15 } name: 'john' } var bankUser = observable(parent);
任務達到觀察局辦公室後,相應的辦公室文員會對任務進行分析,而後會依據對象類型交給相應科室進行處理,常見的有 object
科,另外還有 map
科和 array
科;segmentfault
如今,辦公室文員見傳入的對象是 parent
是個對象,就將其傳遞給 object
科,讓其組織起一塊兒針對該 parent
對象的 」觀察小組「,組名爲 bankUser
。設計模式
object
科接到任務,委派某位科長(如下稱爲 bankUser
科長)組成專項負責此 parent
對象的觀察工做,bankUser
科長接手任務後發現有兩個屬性,其中 child
是對象類型,age
是原始值類型,就分別將任務委派給 child
小科長 和 name
觀察員 O1,child
小科長接到任務後再委派給 name
觀察員 O2 和 age
觀察員 O3,最終執行該任務的人員結構以下:api
觀察員的任務職責咱們已經很熟悉了,當讀寫觀察員對應的數據時將觸發 reportObserved
或 propagateChanged
方法;數組
這裏涉及到兩位科長(bankUser
科長 和 child
小科長),那麼科長的任務職責是什麼呢?
科長的人物職責是起到 管理 做用,它負責統管在他名下的觀察員。好比當咱們讀寫 bankUser.child
對象的 name
屬性時(好比執行語句 bankUser.child.name = 'Jack'
),首先感知到讀寫操做的並不是是 觀察員 O2 而是bankUser
科長,bankUser
科長會告知 child
小科長有數據變動,child
小科長而後再將信息傳達給 name
觀察員 O2 ,而後纔是觀察員 O2 對數據讀寫起反應,這才讓觀察員 O2 發揮做用。
從代碼層面看,咱們看到僅僅是執行 bankUser.child.name = 'Jack'
這一行語句,和咱們日常修改對象屬性並沒有二致。然而在這一行代碼背後其實牽動了一系列的操做。這實際上是 MobX 構建起的一套 」鏡像「 系統,使用者仍舊按平時的方式讀寫對象,然而每一個屬性的讀寫操做實則都鏡像到觀察局 的某個小組具體的操做;很是相似於古代的 」垂簾聽政「 ,看似皇帝坐在文武百官前面,其實真正作出決策響應的是藏在簾後面的那我的。
前幾章中咱們只看到觀察員在活動,然則背後離不開 科長 這一角色機制在背後暗暗的調度。對每項任務,最終都會落實到觀察員採起「一對一」模式監控分配到給本身的觀察項,而每一個觀察員確定是隸屬於某個 」科長「 帶領。在 MobX 系統裏,辦公室、科長和觀察員是密不可分,共同構建起 觀察局 運行體制;
"分工明確,運轉高效",這是最高警長在巡視完觀察員培訓基地後的第一印象,觀察局運轉的每一步的設計都有精細的考量;
先羅列本文故事中人物與 MobX 源碼概念映射關係:
故事人物 | MobX 源碼 | 解釋 |
---|---|---|
警署最高長官 | (無) | MobX 用戶,沒錯,就是你 |
執行官 MobX | MobX | 整個 MobX 運行環境 |
觀察局辦公室(主任、文員) | observable、observable.box 等 | 用於建立 Observable 的 API |
object 科室、map 科室、array 科室 |
observable.object、observable.map、observable.array | 將不一樣複合類型轉換成觀察值的方法 |
科長 | ObservableObjectAdministration | 主要給對象添加 $mobx 屬性 |
觀察員 | ObservableValue 實例 | ObservableValue 實例 |
observable
對應上述故事中的 觀察局辦公室主任 角色,自己不提供轉換功能,主要是起到統一調度做用 —— 這樣 MobX 執行官只須要將命令發給辦公室人員就行,至於內部具體的操做、具體由哪一個科室處理,MobX 執行官不須要關心。
將與 observable 的源碼 相關的源碼稍微整理,就是以下的形式:
var observable = createObservable; // 使用「奇怪」的方式來擴展 observable 函數的功能,就是將 observableFactories 的方法挨個拷貝給 observable Object.keys(observableFactories).forEach(function(name) { return (observable[name] = observableFactories[name]); });
observable
是函數,函數內容就是 createObservable
observable
是對象,對象屬性和 observableFactories
一致也就是說 observable
實際上是 各類構造器的總和,整合了 createObservable
(默認構造器) + observableFactories
(其餘構造器)
本身也能夠在 console 控制檯中打印來驗證一番:
const { observable } = mobx; console.log('observable name:', observable.name); console.log(Object.getOwnPropertyNames(observable));
從如下控制檯輸出的結果來看,observable
的屬性的確來自於createObservable
和 observableFactories
這二者:
文字比較枯燥,用圖來表示就是下面那樣子:
這裏我大體劃分了一下,分紅 4 部份內容來理解:
createObservable
方法剛纔粗略講過,是 MobX API 的 observable 的別名,是一個高度封裝的方法,算是一個總入口,方便用戶調用;該部分對應上述故事中的 觀察局辦公室主任 的角色box
是一個轉換函數,用於將 原值(primitive value) 直接轉換成 ObservableValue 對象;shallowBox
是 box
函數的非 deep 版本;該部分對應上述故事中的 觀察局辦公室文員 的角色;shallow
的版本;該部分對應上述故事中的 科室 部分;如何理解這 4 部分的以前的關係呢?我我的的理解以下:
box
函數轉換能力反而比第一部分更廣,支持將原始值轉換成可觀察值;下面咱們看兩個具體的示例,來輔助消化上面的結論。
示例一:observable.box(obj)
底層就是調用 observable.object(obj)
實現的
var user = { income: 3, name: '張三' }; var bankUser = observable.object(user); var bankUser2 = observable.box(user); console.log(bankUser); console.log(bankUser2);
能夠發現 bankUser2
中的 value
屬性部份內容和 bankUser
是如出一轍的。
示例二:observable.box(primitive)
能行,observable(primitive)
卻會報錯
var pr1 = observable.box(2); console.log(pr1); console.log('--------華麗分割-----------') var pr2 = observable(2); console.log(pr2);
從報錯信息來看,MobX 會友情提示你改用 observable.box
方法實現原始值轉換:
正如上面所言,該函數其實就是 MobX API 的 observable 的 「別名」。因此也是對應上述故事中的 觀察局辦公室主任 角色;
該函數自己不提供轉換功能,只是起到 "轉發" 做用,將傳入的對象轉發給對應具體的轉換函數就好了;
看一下 源碼,
function createObservable(v, arg2, arg3) { // 走向 ① if (typeof arguments[1] === 'string') { return deepDecorator.apply(null, arguments); } // 走向 ② if (isObservable(v)) return v; var res = isPlainObject(v) ? observable.object(v, arg2, arg3) // 走向③ : Array.isArray(v) ? observable.array(v, arg2) // 走向 ④ : isES6Map(v) ? observable.map(v, arg2) // 走向 ⑤ : v; if (res !== v) return res; // 走向 ⑥ fail( process.env.NODE_ENV !== "production" && `The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'` ) }
不難看出實際上是典型的採用了 策略設計模式 ,將多種數據類型(Object、Array、Map)狀況的轉換封裝起來,好讓調用者不須要關心實現細節:
該設計模式參考可參考 深刻理解JavaScript系列(33):設計模式之策略模式
用圖來展現一下具體的走向:
createObservable
的第二個參數是 string 類型,這一點咱們在上一篇文章有詳細論述;observable.box
方法。第一部分的 createObservable
的內容就那麼些,總之只是起了 「嚮導」 做用。是否是比你想象中的要簡單?
接下來咱們繼續看第二部分的 observable.box
方法。
這個方法對應上述故事中的 觀察局辦公室文員 角色,也是屬於辦公室部門的,所起到的做用和 主任
大同小異,只是平時咱們用得並很少罷了。
當我第一次閱讀 官網文檔 中針對有關 observable.box
的描述時:
來回讀了幾回,「盒子」是個啥?它幹嗎用的? 「observable」 和 「盒子」 有半毛錢關係?
直到看完該函數的詳細介紹 boxed values 後,方纔有所感悟,這裏這 box 方法就是將普通函數 「包裝」 成可觀察值,因此 box
是動詞而非名詞 。
準確地理解,observable.box
是一個轉換函數,好比咱們將普通的原始值 "Pekin"(北京)轉換成可觀察值,就可使用:
const cityName = observable.box("Pekin");
原始值 "Pekin" 並不具有可觀察屬性,而通過 box 方法操做以後的 cityName
變量具備可觀察性,好比:
console.log(cityName.get()); // 輸出 'Pekin' cityName.observe(function(change) { console.log(change.oldValue, "->", change.newValue); }); cityName.set("Shanghai"); // 輸出 'Pekin -> Shanghai'
從輸入輸出角度來看,這 box
其實就是將普通對象轉換成可觀察值的過程,轉換過程當中將一系列能力「添加」到對象上,從而得到 「自動響應數值變化」 的能力。
那麼具體這 box
函數是如何實現的呢?直接看 源碼。
box: function(value, options) { if (arguments.length > 2) incorrectlyUsedAsDecorator('box'); var o = asCreateObservableOptions(options); return new ObservableValue( value, getEnhancerFromOptions(o), o.name ); }
發現該方法僅僅是調用 ObservableValue
構造函數,因此 box
方法操做的結果是返回 ObservableValue
實例。
這裏的asCreateObservableOptions
方法僅僅是格式化入參options
對象而已。
總算是講到這個 ObservableValue
類了,該類是理解可觀察值的關鍵概念。這個類對應上述故事中的 觀察員 角色,就是最基層的 name
觀察員 O一、O二、O3 那些。
本篇文章的最終目的也就是爲了講清楚這個 ObservableValue 類,其餘的概念反而是圍繞它而建立起來的。
分析其源碼,將這個類的屬性和方法都拎出來瞧瞧,繪製成類圖大體以下:
你會發現該類 繼承自 Atom 類,因此在理解 ObservableValue
以前必須理解 Atom
。
其實在 3.x 版本的時候,ObservableValue
繼承自BaseAtom
;
隨着升級到 4.x 版本,官方以及廢棄了BaseAtom
,直接繼承自Atom
這個類。
在 MobX 的世界中,任何可以 存儲並管理 狀態的對象都是 Atom,故事中的 觀察員(ObservableValue 實例)本質上就是 Atom(準確的說,而 ObservableValue
是繼承了 Atom
這個基類),Atom
實例有兩項重大的使命:
Atom
類圖以下,從中咱們看到前面幾章中所涉及到的 onBecomeUnobserved
、onBecomeObserved
、reportObserved
、reportChanged
這幾個核心方法,它們都來源於 Atom
這個類:
因此說 Atom 是整個 MobX 的基石並不爲過,全部的自動化響應機制都是創建在這個最最基礎類之上。正如在大天然中,萬物都是由原子(atom)構成的,藉此意義, MobX 中的 」具有響應式的「 對象都是由這個 Atom
類構成的。
(ComputeValue
類 也繼承自 Atom
,Reaction
類的實現得依靠 Atom
,所以不難感知 Atom
基礎重要性)
理論上你只要建立一個 Atom 實例就能融入到 mobx 的響應式系統中,
如何本身建立一個 Atom 呢?
MobX 已經暴露了一個名爲 createAtom 方法,
官方文檔 建立 observable 數據結構和 reactions(反應) 給出了建立一個 鬧鐘 的例子,具體講解了該 createAtom
方法的使用:
... // 建立 atom 就能和 MobX 核心算法交互 this.atom = createAtom( // 第一個參數是 name 屬性,方便後續 "Clock", // 第二個參數是回調函數,可選,當 atom 從 unoberved 狀態轉變到 observed () => this.startTicking(), // 第三個參數也是回調函數,可選,與第二個參數對應,此回調是當 atom 從 oberved 狀態轉變到 unobserved 時會被調用 // 注意到,同一個 atom 有可能會在 oberved 狀態和 unobserved 之間屢次轉換,因此這兩個回調有可能會屢次被調用 () => this.stopTicking() ); ...
同時文中也給出了對應的最佳實踐:
onBecomeObserved
和 onBecomeUnobserved
和咱們面向對象中構造函數與析構函數的做用類似,方便進行資源的申請和釋放。不過 Atom 實例這個仍是偏向底層實現層,除非須要強自定義的特殊場景中,平時咱們推薦直接使用 observable
或者 observable.box
來建立觀察值更爲簡單直接;
MobX 在 Atom
類基礎上,泛化出一個名爲 ObservableValue
類,就是咱們耳熟能詳的 觀察值 了。從代碼層面上來看,實現 ObservableValue
其實就是繼承一下 Atom
這個類,而後再添加許多輔助的方法和屬性就能夠了。
理解完上述的 Atom
對象以後,你就已經理解 ObservableValue
的大部分。接下來就是去理解 ObservableValue
相比 Atom
多出來的屬性和方法,我這裏並不會全講,太枯燥了。只挑選重要的兩部分 —— Intercept & Observe 部分 和 enhancer 部分
在 ObservableValue
類圖中除了常見的 toJSON()
、toString()
方法以外,有兩個方法格外引人注目 —— intercept()
和 observe
兩個方法。
若是把 「對象變動」 做爲事件,那麼咱們能夠在 事件發生以前 和 事件方法以後 這兩個 「切面」 分別能夠安插回調函數(callback),方便程序動態擴展,這屬於 面向切面編程的思想。
不瞭解 AOP 的,能夠查閱 知乎問答-什麼是面向切面編程AOP?
在 MobX 世界裏,將安插在 事件發生以前 的回調函數稱爲 intercept
,將安插在 事件發生以後 的回調函數稱爲 observe
。理解這兩個方法能夠去看 官方中的示例,能快速體會其做用。
這裏稍微進一步講細緻一些,有時候官方文檔會中把 intercept
理解成 攔截器。 這是由於它做用於事件(數據變動)發生以前,所以能夠操縱變動的數據內容,甚至能夠經過返回 null
忽略某次數據變化而不讓它生效。
其做用機制也很直接,該方法調用的最終都是調用實例的 intercept 方法,這樣每次在值變動以前(如下 prepareNewValue
方法執行),都會觸發觀察值上所綁定的全部的 攔截器:
ObservableValue.prototype.prepareNewValue = function(newValue) { ... if (hasInterceptors(this)) { var change = interceptChange(this, { object: this, type: 'update', newValue: newValue }); if (!change) return UNCHANGED; newValue = change.newValue; } // apply modifier ... };
着重裏面的那行語句 if (!change) return UNCHANGED;
,若是你在 intercept
安插的回調中返回 null
的話,至關於告知 MobX 數值沒有變動(UNCHANGED
),既然值沒有變動,後續的邏輯就不會觸發了。
observe
的做用是將回調函數安插在值變動以後(如下 setNewValue
方法調用),一樣是經過調用 notifyListeners
通知全部的監聽器:
ObservableValue.prototype.setNewValue = function(newValue) { ... this.reportChanged(); if (hasListeners(this)) { notifyListeners(this, { type: 'update', object: this, newValue: newValue, oldValue: oldValue }); } };
==========【如下是額外的知識內容,可跳過,不影響主線講解】===========
如何解除安插的回調函數?
Intercept & Observe 這兩個函數返回一個 disposer
函數,這個函數是 解綁函數,調用該函數就能夠取消攔截器或者監聽器 了。這裏有一個最佳實踐,若是不須要某個攔截器或者監聽器了,記得要及時清理本身綁定的監聽函數 永遠要清理 reaction —— 即調用 disposer
函數。
那麼如何實現 disposer
解綁函數這套機制?
以攔截器(intercept)爲例,註冊的時候調用 registerInterceptor
方法:
function registerInterceptor(interceptable, handler) { var interceptors = interceptable.interceptors || (interceptable.interceptors = []); interceptors.push(handler); return once(function() { var idx = interceptors.indexOf(handler); if (idx !== -1) interceptors.splice(idx, 1); }); }
總體的邏輯比較清晰,就是將傳入的 handler
(攔截器)添加到 interceptors
數組屬性中。關鍵是在於返回值,返回的是一個閉包 —— once
函數調用的結果值。
因此咱們簡化一下 disposer
解綁函數的定義:
disposer = once(function() { var idx = interceptors.indexOf(handler); if (idx !== -1) interceptors.splice(idx, 1); });
恰是這個 once
函數是實現解綁功能的核心。
查看這個 once
函數源碼只有寥寥幾行,卻將閉包的精髓運用到恰到好處。
function once(func) { var invoked = false; return function() { if (invoked) return; invoked = true; return func.apply(this, arguments); }; }
該 once
方法其實經過 invoked
變量,控制傳入的 func
函數只調用一次。
回過頭來 disposer
解綁函數,調用一次就會從 interceptors
數組中移除當前攔截器。使用 once
函數後,你不管調用多少次 disposer
方法,最終都只會解綁一次。
因爲 once
是純函數,所以大夥兒能夠提取出來運用到本身的代碼庫中 —— 這也是源碼閱讀的益處之一,借鑑源碼中優秀部分,而後學習吸取,引覺得用。
=======================================================
這部分是在 ObservableValue
構造函數中發揮做用的,其影響的偏偏是最核心的數據屬性:
function ObservableValue(value, enhancer, name, notifySpy) { ... _this.enhancer = enhancer; _this.value = enhancer(value, undefined, name); ... }
在上一篇文章《【用故事解讀 MobX 源碼(四)】裝飾器 和 Enhancer》中有說起過 enhance,在那裏咱們提及過 enhance 其實就是裝飾器(decorator)的有效成分,該有效成分影響的正是本節所講的 ObservableValue
對象。結合 types/modifier.ts 中有各類 Enhancer 的具體內容,就能大體瞭解 enhancer 是如何起到 轉換數值 的做用的,以常見的 deepEnhancer 爲例,當在構造函數中執行 _this.value = enhancer(value, undefined, name);
的時候會進入到 deepEnhance
函數體內:
function deepEnhancer(v, _, name) { // it is an observable already, done if (isObservable(v)) return v; // something that can be converted and mutated? if (Array.isArray(v)) return observable.array(v, { name: name }); if (isPlainObject(v)) return observable.object(v, undefined, { name: name }); if (isES6Map(v)) return observable.map(v, { name: name }); return v; }
這段代碼是否似曾相識?!沒錯,和上一節所述 createObservable
方法幾乎同樣,採用 策略設計模式 調用不一樣具體轉換函數(好比 observable.object
等)。
如今應該可以明白,第一部分的 createObservable
和 第二部分的 observable.box
都是創建在第三部分之上,並且經過第一部分、第二部分以及第三部分得到的觀察值對象都是屬於觀察值對象(ObservableValue
),大同小異,頂多只是「外形」有略微的差異。
經過該 enhancer 部分的講解,咱們發現全部待分析的重要部分都聚焦到第三部分的 observable.object
等這些個轉換方法身上了。
由於結構的緣由,上面先講了最基層的 ObservableValue
部分,如今回來說的 observable.object
方法。從這裏你能大概體會到 MobX 體系中遞歸現象: new ObservableValue
裏面會調用 observable.object
方法,從後面的講解裏你將會看到 observable.object
方法裏面也會調用 new ObservableValue
的操做,因此 遞歸地將對象轉換成可觀察值 就很瓜熟蒂落。
閱讀官方文檔 Observable.object,該 observable.object
方法就是把一個普通的 JavaScript 對象的全部屬性都將被拷貝至一個克隆對象並將克隆對象轉變成可觀察的,並且 observable 是 遞歸應用 的。
observable.object
等方法對應於上述故事中的 科室 部分,用於執行具體的操做。常見的 object
科室是將 plan object
類型數據轉換成可觀察值,map
科室是將 map
類型數據轉換成可觀察值....
咱們查閱 observable.object(object) 源碼,其實就 2 行有效代碼:
object: function(props, decorators, options) { if (typeof arguments[1] === 'string') incorrectlyUsedAsDecorator('object'); var o = asCreateObservableOptions(options); return extendObservable({}, props, decorators, o); },
能夠說 observable.object(object)
其實是 extendObservable({}, object)
的別名,從這裏 extendObservable
方法的第一個參數是 {}
能夠看到,最終產生的觀察值對象是基於全新的對象,不影響原始傳入的對象內容。
講到這裏,會有一種恍然大悟,原來 extendObservable
方法纔是最終大 boss,一切觀察值的建立終歸走到這個函數。查看該方法的 源碼,函數簽名以下:
extendObservable(target, properties, decorators, options)
bankUser
name
方法具體的使用說明參考 官方文檔 extendObservable
將該方法的主幹找出來:
function extendObservable(target, properties, decorators, options) { ... // 第一步 調用 asObservableObject 方法給 target 添加 $mobx 屬性 options = asCreateObservableOptions(options); var defaultDecorator = options.defaultDecorator || (options.deep === false ? refDecorator : deepDecorator); asObservableObject( target, options.name, defaultDecorator.enhancer ); // 第二步 循環遍歷,將屬性通過 decorator(裝飾器) 改造後添加到 target 上 startBatch(); for (var key in properties) { var descriptor = Object.getOwnPropertyDescriptor( properties, key ); var decorator = decorators && key in decorators ? decorators[key] : descriptor.get ? computedDecorator : defaultDecorator; var resultDescriptor = decorator( target, key, descriptor, true ); if (resultDescriptor){ Object.defineProperty(target, key, resultDescriptor); } } endBatch(); return target;
這方法看上去塊頭很大,不過度析起來就 2 大步:
asObservableObject
方法,給 target
生成 $mobx
屬性decorator
改造後從新安裝到 target 上,默認的 decorator 是 deepDecorator
,裝飾器的含義和做用在上一篇文章已講過,點擊 這裏 複習asObservableObject
方法,主要是給目標對象生成 $mobx
屬性;該 $mobx
屬性對應上述故事中的 科長 角色,用於管理對象的讀寫操做。
爲何要添加 $mobx
屬性?其具體做用又是什麼?
經過閱讀源碼,我無從獲知做者添加 $mobx
屬性的理由,但能夠知道 $mobx
的做用是什麼。
首先,$mobx
屬性是一個 ObservableObjectAdministration
對象,類圖以下:
用例子來看看 $mobx
屬性:
var bankUser = observable({ income: 3, name: '張三' }); console.table(bankUser);
下圖紅框處標示出來的就是 bankUser.$mobx
屬性:
咱們進一步經過如下兩行代碼輸出 $mobx
屬性中具體的數據成員和擁有的方法成員:
console.log(`bankUser.$mobx:`, bankUser.$mobx); console.log(`bankUser.$mobx.__proto__:`, bankUser.$mobx.__proto__);
在這麼多屬性中,格外須要注意的是 write
和 read
這兩個方法,這兩個方法算是 $mobx
屬性的靈魂,下面即將會講到,這裏先點名一下。
除此以外還須要關注 $mobx
對象中的 values
屬性,剛初始化的時候該屬性是 {}
空對象,不過注意上面截圖中看到 $mobx.values
是有內容的,這其實不是在這一步完成,而是在接下來要講的第二步中所造成的。
你能夠這麼理解,這一步僅僅是找到擔任科長的人選,仍是光桿司令;下一步纔是正式委派科長到某個科室,那個時候新上任的科長才有權力管束其下屬的觀察員。
decorator
的 「洗禮」這部分就是應用 裝飾器 操做了,默認是使用 deepDecorator
這個裝飾器。裝飾器的應用流程在 上一篇文章 中有詳細講解,直接拿結論過來:
你會發現應用裝飾器的最後一步是在調用 defineObservableProperty
方法時建立 ObservableValue
屬性,對應在 defineObservableProperty 源碼 中如下語句:
var observable = (adm.values[propName] = new ObservableValue( newValue, enhancer, adm.name + '.' + propName, false ));
這裏的 adm
就是 $mobx
屬性,這樣新生成的 ObservableValue
實例就掛載在 $mobx.values[propName]
屬性下。
這樣的設定很巧妙,值得咱們深挖。先看一下下面的示例:
var user = { income: 3, name: '張三' }; var bankUser = observable(user); bankUser.income = 5; console.log(bankUser.income); console.table(bankUser.$mobx.values.income);
在這個案例中,咱們直接修改 bankUser
的 income
屬性爲 5
,一旦修改,此時 bankUser.$mobx.values.income
也會同步修改:
這是怎麼作到的呢?
答案是:經過 generateObservablePropConfig
方法
function generateObservablePropConfig(propName) { return ( observablePropertyConfigs[propName] || (observablePropertyConfigs[propName] = { configurable: true, enumerable: true, get: function() { return this.$mobx.read(this, propName); }, set: function(v) { this.$mobx.write(this, propName, v); } }) ); }
該方法是做用在 decorator
裝飾器其做用期間,用 generateObservablePropConfig
生成的描述符重寫原始對象的描述符,仔細看描述符裏的 get
和 set
方法,對象屬性的 讀寫分別映射到 $mobx.read
和 $mobx.write
這兩個方法中。
在這裏,咱們就能知道掛載 $mobx
屬性的意圖:MobX 爲咱們建立了原對象屬性的 鏡像 操做,全部針對原有屬性的讀寫操做都將鏡像復刻到 $mobx.values
對應 Observable 實例對象上,從而將複雜的操做隱藏起來,給用戶提供直觀簡單的,提升用戶體驗。
以賦值語句 bankUser.income = 5
爲例,這樣的賦值語句咱們平時常常寫,只不過這裏的 bankUser
是咱們 observable.object
操做獲得的,因此 MobX 會同步修改 bankUser.$mobx.values.income
這個 ObservableValue 實例對象,從而觸發 reportChanged 或者 reportObserved 等方法,開啓 響應式鏈 的第一步。
你所作的操做和以往同樣,書寫 bankUser.income = 5
這樣的語句就能夠。而實際上 mobx 在背後默默地作了不少工做,這樣就將簡單的操做留給用戶,而把絕大多數複雜的處理都隱藏給 MobX 框架來處理了。
本小節開始已經說起過遞歸傳遞觀察值,這裏再從代碼層面看一下 遞歸實現觀察值 的原理。這一步是在 decorator
裝飾器應用過程當中,經過 $mobx
掛載對應屬性的 ObservableValue 實例達到的。
對應的操做在剛纔的 5.3
已經講過,仍是在 defineObservableProperty 源碼 那行代碼:
var observable = (adm.values[propName] = new ObservableValue( newValue, enhancer, adm.name + '.' + propName, false ));
如下述的 parent
對象爲例:
var parent = { child: { name: 'tony' } }
當咱們執行 observable(parent)
(或者 new ObservableValue(parent)
、 observable.box(parent)
等建立觀察值的方法),其執行路徑以下:
從上圖就能夠看到,在 decorator
那一步將屬性轉換成 ObservableValue
實例,這樣在總體上看就是遞歸完成了觀察值的轉換 —— 把 child
和它下屬的屬性也轉換成可觀察值。
請分析 observable.map
和 observable.array
的源碼,看看它們和 observable.object
方法之間的差異在哪兒。
本文重點是講 Observable
類,與之相關的類圖整理以下:
ObservableValue
繼承自 Atom
,並實現一系列的 接口;ObservableObjectAdministration
是 鏡像操做管理者,它主要經過 $mobx
屬性來操控管理每一個觀察值 ObservableValue
intercept
和 observe
,用「面向切口」編程的術語來說,這兩個方法就是兩個 切口,分別做用於數值更改先後,方便針對數據狀態作一系列的響應;本文中出現不少 observable
相關的單詞,稍做總結:
ObservableValue
是一個普通的 class,用於表示 觀察值 這個概念。observable
是一個函數,也是 mobx 提供的 API,等於 createObservable
,表明操做,該操做過程當中會根據狀況調用 observable.object
(或者 observable.array
、observable.map
)等方法,最終目的是爲了建立 ObservableValue
對象。extendObservable
,這是一個工具函數,算是比較底層的方法,該方法用來向已存在的目標對象添加 observable 屬性;上述的 createObservable
方法其實也是借用該方法實現的;MobX 默認會遞歸將對象轉換成可觀察屬性,這主要是得益於 enhancer
在其中發揮的做用,由於每一次 Observable 構造函數會對傳入的值通過 enhancer
處理;
有人不由會問,既然提供 observable
方法了,那麼 observable.box
方法存在的意義是什麼?答案是,因爲它直接返回的是 ObservableValue
,它相比普通的 observable
建立的觀察值,提供更加細粒度(底層)的操做;
好比它除了能像正常觀察值同樣和 autorun
搭配使用以外,建立的對象還直接擁有 intercept
和 observe
方法:
var pr1 = observable.box(2); autorun(() => { console.log('value:', pr1.get()); }); pr1.observe(change => { console.log('change from', change.oldValue, 'to', change.newValue); }); pr1.set(3); // 如下是輸出結果: // value: 2 // value: 3 // change from 2 to 3
固然 MobX 考慮也很周全,還單獨提供 Intercept & Observe 兩個工具函數,以函數調用的方式給觀察值新增這兩種回調函數。
所以下述兩種方式是等同的,能夠本身試驗一下:
// 調用 observe 屬性方法 pr1.observe(change => { console.log('change from', change.oldValue, 'to', change.newValue); }); // 使用 observe 工具函數能夠達到相同的目的 observe(pr1, change => { console.log('change from', change.oldValue, 'to', change.newValue); }):
本文針對 MobX 4 源碼講解,而在 MobX 5 版本中的 Observable
類則是採用 proxy
來實現 Observable,總體思路和上述的並沒有二致,只是在細節方面將 Object.defineProperty
替換成 new Proxy
的寫法而已,感興趣的同窗建議先閱讀 《抱歉,學會 Proxy 真的能夠隨心所欲》瞭解 Proxy
的寫法,而後去看一下 MobX 5 中的 observable.object 方法已經改用 createDynamicObservableObject 來建立 proxy,所建立的 proxy 模型來自於 objectProxyTraps 方法;若有機會將在後續的文章中更新這方面的知識。
用故事講解 MobX 源碼的系列文章至此告一段落,後續以散篇的形式發佈跟 MobX 相關的文章。
下面的是個人公衆號二維碼圖片,歡迎關注,及時獲取最新技術文章。