Mobx 思想的實現原理

Mobx 思想的實現原理

Mobx 最關鍵的函數在於 autoRun,舉個例子,它能夠達到這樣的效果:前端

const obj = observable({
    a: 1,
    b: 2
})

autoRun(() => {
    console.log(obj.a)
})

obj.b = 3 // 什麼都沒有發生
obj.a = 2 // observe 函數的回調觸發了,控制檯輸出:2複製代碼

咱們發現這個函數很是智能,用到了什麼屬性,就會和這個屬性掛上鉤,今後一旦這個屬性發生了改變,就會觸發回調,通知你能夠拿到新值了。沒有用到的屬性,不管你怎麼修改,它都不會觸發回調,這就是神奇的地方。react

autoRun 的用途

使用 autoRun 實現 mobx-react 很是簡單,核心思想是將組件外面包上 autoRun,這樣代碼中用到的全部屬性都會像上面 Demo 同樣,與當前組件綁定,一旦任何值發生了修改,就直接 forceUpdate,並且精確命中,效率最高。es6

依賴收集

autoRun 的專業名詞叫作依賴收集,也就是經過天然的使用,來收集依賴,當變量改變時,根據收集的依賴來判斷是否須要更新。typescript

實現步驟拆解

爲了兼容,Mobx 使用了 Object.defineProperty 攔截 gettersetter,可是沒法攔截未定義的變量,爲了方便,咱們使用 proxy 來說解,並且能夠監聽未定義的變量哦。性能優化

步驟一 存儲結構

衆所周知,事件監聽是須要預先存儲的,autoRun 也同樣,爲了知道當變量修改後,哪些方法應該被觸發,咱們須要一個存儲結構。微信

首先,咱們須要存儲全部的代理對象,讓咱們不管拿到原始對象,仍是代理對象,都能快速的找出是否有對應的代理對象存在,這個功能用在判斷代理是否存在,是否合法,以及同一個對象不會生成兩個代理。frontend

代碼以下:函數

const proxies = new WeakMap()

function isObservable<T extends object>(obj: T) {
    return (proxies.get(obj) === obj)
}複製代碼

重點來了,第二個要存儲的是最重要的部分,也就是全部監聽!當任何對象被改變的時候,咱們須要知道它每個 key 對應着哪些監聽(這些監聽由 autoRun 註冊),也就是,最終會存在多個對象,每一個對象的每一個 key 均可能與多個 autoRun 綁定,這樣在更新某個 key 時,直接觸發與其綁定的全部 autoRun 便可。性能

代碼以下:學習

const observers = new WeakMap<object, Map<PropertyKey, Set<Observer>>>()複製代碼

第三個存儲結構就是待觀察隊列,爲了使同一個調用棧屢次賦值僅執行一次 autoRun,全部待執行的都會放在這個隊列中,在下一時刻統一執行隊列並清空,執行的時候,當前全部 autoRun 都是在同一時刻觸發的,因此讓相同的 autoRun 不用觸發屢次便可實現性能優化。

const queuedObservers = new Set()複製代碼

代碼以下:

咱們還要再存儲兩個全局變量,分別是是否在隊列執行中,以及當前執行到的 autoRun

代碼以下:

let queued = false
let currentObserver: Observer = null複製代碼

步驟二 將對象加工可觀察

這一步講解的是 observable 作了哪些事,首先第一件就是,若是已經存在代理對象了,就直接返回。

代碼以下:

function observable<T extends object>(obj: T = {} as T): T {
    return proxies.get(obj) || toObservable(obj)
}複製代碼

咱們繼續看 toObservable 函數,它作的事情是,實例化代理,並攔截 get set 等方法。

咱們先看攔截 get 的做用:先拿到當前要獲取的值 result,若是這個值在代理中存在,優先返回代理對象,不然返回 result 自己(沒有引用關係的基本類型)。

上面的邏輯只是簡單返回取值,並無註冊這一步,咱們在 currentObserver 存在時纔會給對象當前 key 註冊 autoRun,而且若是結果是對象,又不存在已有的代理,就調用自身 toObservable 再遞歸一遍,因此返回的對象必定是代理。

registerObserver 函數的做用是將 targetObj -> key -> autoRun 這個鏈路關係存到 observers 對象中,當對象修改的時候,能夠直接找到對應 keyautoRun

那麼 currentObserver 是何時賦值的呢?首先,並非訪問到 get 就要註冊 registerObserver,必須在 autoRun 裏面的才符合要求,因此執行 autoRun 的時候就會將當前回調函數賦值給 currentObserver,保證了在 autoRun 函數內部全部監聽對象的 get 攔截器都能訪問到 currentObserver。以此類推,其餘 autoRun 函數回調函數內部變量 get 攔截器中,currentObserver 也是對應的回調函數。

代碼以下:

const dynamicObject = new Proxy(obj, {
    // ...
    get(target, key, receiver) {
        const result = Reflect.get(target, key, receiver)

        // 若是取的值是對象,優先取代理對象
        const resultIsObject = typeof result === 'object' && result
        const existProxy = resultIsObject && proxies.get(result)

        // 將監聽添加到這個 key 上
        if (currentObserver) {
            registerObserver(target, key)
            if (resultIsObject) {
                return existProxy || toObservable(result)
            }
        }

        return existProxy || result
    }),
    // ...
})複製代碼

setter 過程當中,若是對象產生了變更,就會觸發 queueObservers 函數執行回調函數,這些回調都在 getter 中定義好了,只須要把當前對象,以及修改的 key 傳過去,直接觸發對應對象,當前 key 所註冊的 autoRun 便可。

代碼以下:

const dynamicObject = new Proxy(obj, {
    // ...
    set(target, key, value, receiver) {
        // 若是改動了 length 屬性,或者新值與舊值不一樣,觸發可觀察隊列任務
        if (key === 'length' || value !== Reflect.get(target, key, receiver)) {
            queueObservers<T>(target, key)
        }

        // 若是新值是對象,優先取原始對象
        if (typeof value === 'object' && value) {
            value = value.$raw || value
        }

        return Reflect.set(target, key, value, receiver)
    },
    // ...
})複製代碼

沒錯,主要邏輯已經所有說完了,新對象之因此能夠檢測到,是由於 proxyget 會觸發,這要多謝 proxy 的強大。

可能有人問 Object.defineProperty 爲何不行,緣由很簡單,由於這個函數只能設置某個 keygetter setter~。

symbol proxy reflect 這三劍客能作的事還有不少不少,這僅僅是實現 Object.observe 而已,還有更強大的功能能夠挖掘。

總結

es6 真的很是強大,呼籲你們拋棄 ie11,擁抱美好的將來!


本文對你有幫助?歡迎掃碼加入前端學習小組微信羣:

相關文章
相關標籤/搜索