Mobx 源碼解析 一(observable)

前言

在Git 找到Mobx 的源碼, 發現其是使用TypeScript 編寫,由於我對Typescrit 沒有項目經驗,因此我先會將其編譯成JavaScript,因此咱們能夠運行以下腳本或者從CDN直接下載一份編譯過的源碼,咱們能夠選擇umd 規範腳本:html

  1. git clone git@github.com:mobxjs/mobx.git
  2. npm i
  3. npm run quick-build

我直接從CDN 下載了一份源碼, 而後進行分析。git

Demo

首先咱們從一個最基本的Demo開始,來看Mobx 的基本使用方式:github

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 --
})

複製代碼

咱們的界面很是簡單,如圖: ajax

圖1
兩個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 兩個方法,分別是observableautorun,是的,是這樣兩個方法,讓incomeLabel 在點擊按鈕的時候實時的發生了變化,因此咱們接下來會對這兩個方法進行深刻分析,這一章節咱們會先分析observable 先進行分析。 咱們先打開Mobx的源碼, 若是咱們用Vscode 打開這個源碼,咱們能夠用快捷鍵Ctrl + K Ctrl + 0 將代碼都摺疊起來, 而後在打開, 找到exports 的代碼塊,咱們能夠查看mobx 都暴露出了哪些方法:npm

圖2
暴露了一些列方法,咱們後續會使用。

observable,翻譯成中文就是能夠觀測的, 咱們如今來調試這個方法, 咱們能夠const bankUser = mobx.observable({ 這一行打一個斷點,而後F11,跳進去,發現源碼對應的是一個createObservable 方法,也就是建立一個能夠觀察的對象:bash

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 的基本邏輯:app

  1. 若是傳入的第二個參數是一個字符串, 則直接調用deepDecorator$$1.apply(null, arguments);
  2. 判斷第一個參數是否已是一個可觀察的對象了,若是已是可觀察的對象了,就直接返回這個對象
  3. 判斷第一個參數是什麼類型,而後調用不一樣的方法, 總共有三種類型: 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 方法,接下來咱們在繼續分析這個方法, 其代碼以下:ide

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); 生成的了一個簡單的對象:ui

var defaultCreateObservableOptions$$1 = {
    deep: true,
    name: undefined,
    defaultDecorator: undefined,
    proxy: true
};
複製代碼

o.proxy 的值爲true, 因此會走else 邏輯分支, 因此接下來咱們一一分析else 分支中的每一條代碼。spa

  1. var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o); 這個是跟裝飾器有關的邏輯,咱們先跳過
  2. var base = extendObservable$$1({}, undefined, undefined, o);o對象進行了加工處理,變成了一個Symbol 數據類型。

這一步操做很是重要,給一個空對象添加了一個$mobx$$1(var $mobx$$1 = Symbol("mobx administration");)的屬性, 其值是一個 ObservableObjectAdministration 類型對象,其write 方法在後續數據攔截中會調用。

圖3
  1. var proxy = createDynamicObservableObject$$1(base); 這個方法,最爲核心, 對這個對象進行了代理(Proxy)

圖4

對這個對象的屬性的get, set, has, deleteProperty, ownKeys, preventExtensions方法進行了代理攔截,這個是Mobx 事件數據添加的一個核心點。

  1. 第三點的proxy 其實只是初始化了一個簡單的代理對象,可是沒有與咱們須要觀察的target(也就是mobx.observable方法傳遞進來的須要被觀察的對象)關聯起來, extendObservableObjectWithProperties$$1(proxy, props, decorators, defaultDecorator); 方法會遍歷target 的屬性,將其賦值給proxy對象, 而後咱們mobx.observable 裏的對象都被代理了,也就是實現了對屬性操做的攔截處理。

  2. 在第四點extendObservableObjectWithProperties$$1 方法中, 最終會給原始的對象的屬性進行裝飾,經過查看function 的 call stack 得知,最後對調用ObservableObjectAdministration 的addObservableProp 方法, 針對每個propName(原始對象的Key)生成一個ObservableValue 對象,而且保存在ObservableObjectAdministration 對象的values

圖三中發現, 真正實現數據攔截的就是objectProxyTraps 攔截器, 下一章節,咱們須要對這個攔截器進行深刻分析,着重看get,set如何實現了數據攔截。

  1. return proxy; 最終將返回一個已經被代理過的對象,替換原生對象。

bankUser 對象就是一個已經被代理了的對象,而且包含了一個Symbol 類型的新的屬性。

const bankUser = mobx.observable({
    name: 'Ivan Fan',
    income: 3,
    debit: 2
});
複製代碼

總結

  1. observable 首先傳入一個原始對象(能夠傳入多種類型的數據: array, map, object, 如今只分析object 類型的狀況)
  2. 建立一個空的Object 對象,而且添加一些默認屬性(var base = extendObservable$$1({}, undefined, undefined, o);), 包括一個Symbol類型的屬性,其值是一個ObservableObjectAdministration 類型的對象.
  3. 將這個對象用ES6Proxy 進行了代理, 會攔截這個對象的一些列操做(get, set...) var proxy = new Proxy(base, objectProxyTraps);
  4. 將原始對象,進行遍歷,將其全部的本身的屬性掛載在新建立的空對象中
  5. 返回已經加工處理的對象bankUser
  6. 後續就能夠監聽這個對象的相應的操做了。
  7. 加工後的對象以下圖所示, 後面操做的對象,就是以下這個對象,可是observable 方法,其實只是作到了以下圖的第二步(2), 第三步(3)的observers屬性仍是一個沒有任何值的Set 對象,在後續分析autorun 方法中,會涉及到在何時去給它賦值

相關文章
相關標籤/搜索