前言
在Git 找到Mobx 的原始碼, 發現其是使用TypeScript 編寫,因為我對Typescrit 沒有專案經驗,所以我先會將其編譯成JavaScript,所以我們可以執行如下指令碼或者從CDN直接下載一份編譯過的原始碼,我們可以選擇umd 規範指令碼:
git clone git@github.com:mobxjs/mobx.git
npm i
npm run quick-build
Demo
首先我們從一個最基本的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 --
})複製程式碼
我們的介面非常簡單,如圖:
{
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({
對o物件進行了加工處理,變成了一個
}, undefined, undefined, 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
裡的物件都被代理了,也就是實現了對屬性操作的攔截處理。 -
在第四點
extendObservableObjectWithProperties$$1
方法中, 最終會給原始的物件的屬性進行裝飾,通過檢視function 的 call stack 得知,最後對呼叫ObservableObjectAdministration 的addObservableProp 方法, 針對每一個propName(原始物件的Key)生成一個ObservableValue 物件,並且儲存在ObservableObjectAdministration 物件的values中
從圖三中發現, 真正實現資料攔截的就是objectProxyTraps
攔截器, 下一章節,我們需要對這個攔截器進行深入分析,著重看get
,set
如何實現了資料攔截。
return proxy;
最終將返回一個已經被代理過的物件,替換原生物件。
bankUser 物件就是一個已經被代理了的物件,並且包含了一個
Symbol
型別的新的屬性。
const bankUser = mobx.observable({
name: 'Ivan Fan', income: 3, debit: 2
});
複製程式碼
總結
- observable 首先傳入一個原始物件(可以傳入多種型別的資料:
array
,map
,object
, 現在只分析object 型別的情況) - 建立一個空的Object 物件,並且新增一些預設屬性(
var base = extendObservable$$1({
), 包括一個
}, undefined, undefined, o);Symbol
型別的屬性,其值是一個ObservableObjectAdministration
型別的物件. - 將這個物件用ES6 的Proxy 進行了代理, 會攔截這個物件的一些列操作(
get
,set
…)var proxy = new Proxy(base, objectProxyTraps);
- 將原始物件,進行遍歷,將其所有的自己的屬性掛載在新建立的空物件中
- 返回已經加工處理的物件
bankUser
- 後續就可以監聽這個物件的相應的操作了。
- 加工後的物件如下圖所示, 後面操作的物件,就是如下這個物件,但是observable 方法,其實只是做到了如下圖的第二步(2), 第三步(3)的observers屬性還是一個沒有任何值的Set 物件,在後續分析autorun 方法中,會涉及到在什麼時候去給它賦值