Mobx 原始碼解析 一(observable)
在Git 找到 Mobx 的原始碼, 發現其是使用 TypeScript 編寫,因為我對Typescrit 沒有專案經驗,所以我先會將其編譯成 JavaScript ,所以我們可以執行如下指令碼或者從CDN直接下載一份編譯過的原始碼,我們可以選擇 umd 規範指令碼:
git clone [email protected]:mobxjs/mobx.git npm i npm run quick-build
我直接從CDN 下載了一份原始碼, 然後進行分析。
Demo
首先我們從一個最基本的 ofollow,noindex">Demo 開始,來看 Mobx 的基本使用方式:
const addBtn = document.getElementById('add') const minusBtn = document.getElementById('minus') const incomeLabel = document.getElementById('incomeLabel') const bankUser = mobx.observable({ name: 'Ivan Fan', income: 3, debit: 2 }); const incomeDisposer = mobx.autorun(() => { incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}` }) addBtn.addEventListener('click', ()=> { bankUser.income ++ }) minusBtn.addEventListener('click', () => { bankUser.income -- }) 複製程式碼
我們的介面非常簡單,如圖:

兩個Button , 一個label. 我們在js 檔案中,我們給兩個按鈕添加了**click** 事件,事件的主體非常簡單`bankUser.income ++` `bankUser.income --`, 就是對`bankuser` 的`income` 屬性進行了自增或者自減,非常神奇, 當我們點選對應的按鈕的時候, 中間的label 的內容發生了變化。但是我們在Button 的點選事件中並沒有去操作**incomeLabel** 的內容,但是其內容確實隨著點選事件,實時發生了變化。究其原因,只有以下程式碼對**incomeLabel** 的text 進行了處理: ``` const incomeDisposer = mobx.autorun(() => { incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}` }) ``` 這就是**Mobx** 的最簡單神祕的功能,我們可以先從此開始深入研究它。
observable
從上面的JS檔案中,我們發現其中引用了 mobx 兩個方法,分別是 observable 和autorun,是的,是這樣兩個方法,讓 incomeLabel 在點選按鈕的時候實時的發生了變化,所以我們接下來會對這兩個方法進行深入分析,這一章節我們會先分析observable先進行分析。 我們先開啟Mobx的 原始碼 , 如果我們用 Vscode 開啟這個原始碼,我們可以用快捷鍵 Ctrl + K
Ctrl + 0
將程式碼都摺疊起來, 然後在開啟, 找到 exports 的程式碼塊,我們可以檢視mobx 都暴露出了哪些方法:

暴露了一些列方法,我們後續會使用。
observable,翻譯成中文就是 可以觀測的 , 我們現在來除錯這個方法, 我們可以 const bankUser = mobx.observable({
這一行打一個斷點,然後 F11
,跳進去,發現原始碼對應的是一個createObservable方法,也就是建立一個可以觀察的物件:
var observable$$1 = createObservable; function createObservable(v, arg2, arg3) { if (typeof arguments[1] === "string") { return deepDecorator$$1.apply(null, arguments); } if (isObservable$$1(v)) return v; var res = isPlainObject$$1(v) ? observable$$1.object(v, arg2, arg3) : Array.isArray(v) ? observable$$1.array(v, arg2) : isES6Map$$1(v) ? observable$$1.map(v, arg2) : v; if (res !== v) return res; // otherwise, just box it fail$$1(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)'"); } 複製程式碼
上面程式碼很簡單,引數有三個,但是我們在呼叫的時候,值傳遞了一個引數, 所以我們暫且只要關心第一個引數r.以下是這個functin 的基本邏輯:
- 如果傳入的第二個引數是一個字串, 則直接呼叫deepDecorator$$1.apply(null, arguments);
- 判斷第一個引數是否已經是一個可觀察的物件了,如果已經是可觀察的物件了,就直接返回這個物件
- 判斷第一個引數是什麼型別,然後呼叫不同的方法, 總共有三種類型:object,array,map(ES 的Map 資料型別), 分別呼叫:
observable$$1.object
,observable$$1.array
,observable$$1.map
方法, 那這個observable$$1又是什麼呢?在第一行var observable$$1 = createObservable;
表面就是createObservable方法。但是這個方法就短短几行程式碼,並沒有object, array, map著三個方法, 我們發現在這個方法下面有 observableFactories 物件,其是一個工廠物件,用來給createObservable新增方法,其定義了這三個方法,並且通遍歷過Object.keys(observableFactories).forEach(function (name) { return (observable$$1[name] = observableFactories[name]); });
因為在我們的Demo 中我們傳遞的是一個Object, 所以會呼叫 observable$$1.object
方法,接下來我們在繼續分析這個方法, 其程式碼如下:
object: function (props, decorators, options) { if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("object"); var o = asCreateObservableOptions$$1(options); if (o.proxy === false) { return extendObservable$$1({}, props, decorators, o); } else { var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o); var base = extendObservable$$1({}, undefined, undefined, o); var proxy = createDynamicObservableObject$$1(base); extendObservableObjectWithProperties$$1(proxy, props, decorators, defaultDecorator); return proxy; } }, 複製程式碼
var o = asCreateObservableOptions$$1(options);
生成的了一個簡單的物件:
var defaultCreateObservableOptions$$1 = { deep: true, name: undefined, defaultDecorator: undefined, proxy: true }; 複製程式碼
o.proxy
的值為 true
, 所以會走 else
邏輯分支, 所以接下來我們一一分析 else
分支中的每一條程式碼。
-
var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o);
這個是跟裝飾器有關的邏輯,我們先跳過 -
var base = extendObservable$$1({}, undefined, undefined, o);
對o物件進行了加工處理,變成了一個Symbol
資料型別。
這一步操作非常重要,給一個空物件添加了一個 $mobx$$1
( var $mobx$$1 = Symbol("mobx administration");
)的屬性, 其值是一個 ObservableObjectAdministration
型別物件,其 write
方法在後續資料攔截中會呼叫。

-
var proxy = createDynamicObservableObject$$1(base);
這個方法,最為 核心 , 對這個物件進行了代理(Proxy)

對這個物件的屬性的 get
, set
, has
, deleteProperty
, ownKeys
, preventExtensions
方法進行了代理攔截,這個是 Mobx 事件資料新增的一個核心點。
- 第三點的
proxy
其實只是初始化了一個簡單的代理物件,但是沒有與我們需要觀察的target
(也就是mobx.observable
方法傳遞進來的需要被觀察的物件)關聯起來,extendObservableObjectWithProperties$$1(proxy, props, decorators, defaultDecorator);
方法會遍歷target
的屬性,將其賦值給proxy
物件, 然後我們mobx.observable
裡的物件都被代理了,也就是實現了對屬性操作的攔截處理。
從 圖三 中發現, 真正實現資料攔截的就是 objectProxyTraps
攔截器, 下一章節,我們需要對這個攔截器進行深入分析,著重看 get
, set
如何實現了資料攔截。
-
return proxy;
最終將返回一個已經被代理過的物件,替換原生物件。
bankUser 物件就是一個已經被代理了的物件,並且包含了一個 Symbol
型別的新的屬性。
const bankUser = mobx.observable({ name: 'Ivan Fan', income: 3, debit: 2 }); 複製程式碼