博客原文:github mobx observable-an-objectgit
文本是 mobx 源碼解讀系列 第一篇es6
本系列文章所有采用 mobx 較新版本:v5.13.0github
在閱讀以前,但願你對如下技術有所瞭解或實踐,否則可能會影響你對本文的理解編程
ES6 代理:proxyapi
├── src
│ ├── api // 進行劫持方法和做出反應的 api
│ ├── core // 全局狀態、函數等
│ ├── types // 重寫關於 object,array 等類型的 api
│ ├── utils // 工具方法
│ ├── internal.ts
│ └── mobx.ts // 全局導出
└── package.json
複製代碼
兩個關鍵詞:屬性劫持,遞歸編程語言
屬性劫持,能夠理解爲是在編程語言層面上進行編程,這點在 proxy & reflect
中體現的尤其明顯
經過 Object.defineProperty
或 proxy
能夠實如今獲取或修改對象屬性時,作一些額外的操做
mobx5 版本默認劫持重構爲了 proxy
,主要是 proxy
相對於 Object.defineProperty
較穩定,而且劫持的手段更多,具體就不展開了
對象的屬性值可能也是個對象或數組,那麼若是是引用類型就進行遞歸劫持
先對總體步驟有個大概的瞭解,方便後面的理解
裝飾器書寫有帶括號和不帶括號的,但最後都要求返回 descriptor
@observable obj ...
@log({ name: 'lawler' }) obj2 ...
根據屬性值的類型,調用的相應 enhancer(即劫持器,後面會說到)進行劫持
判斷是否爲引用類型,若是是,則遞歸劫持
源碼截圖上有不少對應代碼的註釋,也請一塊兒閱讀
能夠看到,observable 用 createObservable 賦值
並將 observableFactories 的 keys 遍歷,將其屬性掛在 observable 下(同時也是掛在 createObservable 下)
利用剛 observableFactories 掛上來的 object, array 等屬性,來根據變量類型調用相應方法劫持
注意 observable 和 createObservable 是一個對象而且在相互調用,這個彎要注意
object 函數接收三個參數,第三個參數爲 options 能夠定製化劫持方式
const person = observable({
name: 'lawler',
get labelText() {
return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
},
setAge(age) {
his.age = age;
}
}, { // 此爲第二個參數 decorators
setAge: action
} /*, 這裏傳第三個 options 參數 */);
複製代碼
若是不傳 options 返回默認的 defaultCreateObservableOptions,若是傳了 options 就按照用戶寫的來
回到第 3 步,若是 o.proxy 爲 false 採用
Object.defineProperty
劫持,不然採用proxy
劫持
options.deep 默認爲 true,因此默認取 deepDecorator
deepDecorator 來自於 createDecoratorForEnhancer(這個是後面的重點,先放這),須要傳一個參數 enhancer
須要 deepDecorator 就傳 deepEnhancer;須要 shallowDecorator 就傳 shallowEnhancer ...
deepEnhancer 其實就是根據變量的不一樣類型,調用 observable 的不一樣參數,如 object, array 來進行劫持
有沒有似曾相識的感受,其實就和步驟 2 的 createObservable 是同樣的
如今必定要牢記:enhancer 其實就是一個劫持器,裏面提供了劫持各類類型的方法(至關於 observable)
因此 @observable obj ... 不會改變原來對象的
若是 properties 不爲空的話(即 Object.defineProperty 劫持)則直接進行調用 extendObservableObjectWithProperties
若是走 proxy 劫持,在獲取到代理對象後(const proxy = createDynamicObservableObject(base)),主動調用 extendObservableObjectWithProperties。見
章節一 步驟3
因此能看出來這個函數主要目的就是初始化不一樣劫持狀況下目標產物的屬性:initializeInstance(記住這個函數,後面講)和 asObservableObject
for of 不用說
獲取到準備好的用來處理該對象的 decorator,而後傳入屬性裝飾器須要的三個基本參數:target, key, descriptor
返回的結果就是劫持完該屬性後的 resultDescriptor,再經過 Object.defineProperty 寫入 target(即被 proxy 代理的 base 空對象)中
由此完成當前層的當前 key 的劫持
爲了方便你們理解能夠參考下:codesandbox: decorator demo
看完
章節一 步驟5
你可能會疑惑,你怎麼獲得我當前任何裝飾器、任何數據類型、任何定製化劫持的 decorator 函數的如今就回到那裏,以前埋下了伏筆:createDecoratorForEnhancer
咱們從 getDefaultDecoratorFromObjectOptions 出發,裏面經過調用 createDecoratorForEnhancer 並傳入 deepEnhancer 獲得 deepDecorator 供咱們使用
如今看看 createDecoratorForEnhancer 怎麼生成各類 decorator。這是一個很是重難點,請耐心反覆閱讀
首先,調用 createPropDecorator 函數拿到 decorator,申明一個變量 res,將 enhancer 掛在下面,而後返回 res
在 createPropDecorator 傳了兩個參數,第一個是 boolean,第二個是函數
該函數是裝飾器的代理函數,是爲了在 createDecoratorForEnhancer 層面上拿到 enhancer
函數參數
的能夠看到總體是個建立 decoratorFactory 工廠的函數,主要就是根據 enhancer 的不一樣,返回相應的工廠
在 decoratorFactory 中主要就是統一了 @decorator obj 和 @decorator('decoratorArguments') obj2 的用法
decorator 函數返回的是 createPropertyInitializerDescriptor 執行的結果,其具體返回的是個 descriptor
再申明一遍,decorator 函數執行後返回的是 descriptor,而這正是咱們須要的 resultDescriptor,見
章節一 步驟7
在 decoratorFactory 最後經過 quacksLikeADecorator 判斷裝飾器爲哪一種類型
若是爲 @decorator obj,則直接 decorator 返回 descriptor(decorator.apply(null, arguments as any))
若是爲 @decorator('decoratorArguments') obj2,則返回 decorator(在書寫時執行)
值得一提的是,經過第二種方式傳的參數,就是 decoratorFactory 的 arguments 對象,因此爲啥 quacksLikeADecorator 利用的是 arguments 來判斷
是否是終於看到熟悉的 get,set 了,裏面調用了 initializeInstance,還記得在
章節一 步驟6
說的這個函數吧在這個方法裏面除了添加 addHiddenProp,還調用了 propertyCreator,這就是
章節二 步驟3
createPropDecorator 傳進來的第二個參數,而後放進了 target[mobxPendingDecorators]![prop] 屬性中,供 extendObservable 使用提醒一下,整個
章節二
是創建在章節一 步驟5
中的initializeInstance 在初始化 base 空對象會調用,操做對象時也會調用
章節二 步驟2
,看看 createPropDecorator 傳的第二個參數能發現調用了這樣一個函數:asObservableObject,其傳入參數爲原始對象的一個屬性值,而且鏈式調用了 addObservableProp
其實咱們均可以猜想這個函數幹了啥,就是經過 enhancer,把 propertyName 屬性賦上劫持後的 initialValue
經過 target(被裝飾器修飾的 target,爲整個對象)拿到對象的 ObservableObjectAdministration(若是對象屬性值也是對象,則該屬性值也會擁有 adm)
並將其掛到 target 的 $mobx 屬性下,方便後面暴露 api 使用
拿到對象管理器後調用 addObservableProp 方法,將對象當前層的當前 propertyName 劫持
能夠看出 adm 其實也是個封裝類,具體圍繞 values 展開,而 values 是個 Map,鍵爲 PropertyKey,值爲 ObservableValue
像 read,write 等方法,最後都是調用的 ObservableValue 提供的 api
經過 new ObservableValue 傳入 newValue、enhancer 獲得劫持後的 observable
再填充 this.values,以後的操做統一交給 adm 管理
能夠看到 ObservableValue 圍繞 value 展開,經過 enhancer 進行劫持,這裏才真正的使用到 enhancer
這裏若是被劫持屬性值也是對象,調用 enhancer 劫持,後續會遞歸以前全部的步驟
此外 ObservableValue 提供了 get,set 方法和 Object 的 api,如 toString
最後在回到
章節二 步驟4
梳理下:createPropertyInitializerDescriptor 執行後返回 get 和 set,它們裏面都調用了 initializeInstanceinitializeInstance 調用緩存好的 propertyCreator,裏面經過 asObservableObject 拿到 adm 來進行各類操做
回憶咱們改變一個 observable 對象後,依然是劫持的對吧
@observable obj = { a: 1 };
obj = { b: 2, c: { d: 3 } };
其實就是先把新值劫持下再賦值
咱們知道 adm 是被劫持後的 object 的核心,因此拿到 adm 就可能進行各類操做
經過
章節三 步驟1
緩存的 $mobx 就能夠辦到,問題迎刃而解
歡迎在 mobx 源碼解讀 issue 中討論~
碼字不易,喜歡的記得點 💛 哦