Mobx 最關鍵的函數在於 autoRun
,舉個例子,它能夠達到這樣的效果:前端
const obj = observable({
a: 1,
b: 2
})
autoRun(() => {
console.log(obj.a)
})
obj.b = 3 // 什麼都沒有發生
obj.a = 2 // observe 函數的回調觸發了,控制檯輸出:2複製代碼
咱們發現這個函數很是智能,用到了什麼屬性,就會和這個屬性掛上鉤,今後一旦這個屬性發生了改變,就會觸發回調,通知你能夠拿到新值了。沒有用到的屬性,不管你怎麼修改,它都不會觸發回調,這就是神奇的地方。react
使用 autoRun
實現 mobx-react
很是簡單,核心思想是將組件外面包上 autoRun
,這樣代碼中用到的全部屬性都會像上面 Demo 同樣,與當前組件綁定,一旦任何值發生了修改,就直接 forceUpdate
,並且精確命中,效率最高。es6
autoRun
的專業名詞叫作依賴收集,也就是經過天然的使用,來收集依賴,當變量改變時,根據收集的依賴來判斷是否須要更新。typescript
爲了兼容,Mobx 使用了 Object.defineProperty
攔截 getter
和 setter
,可是沒法攔截未定義的變量,爲了方便,咱們使用 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
對象中,當對象修改的時候,能夠直接找到對應 key
的 autoRun
。
那麼 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)
},
// ...
})複製代碼
沒錯,主要邏輯已經所有說完了,新對象之因此能夠檢測到,是由於 proxy
的 get
會觸發,這要多謝 proxy
的強大。
可能有人問 Object.defineProperty
爲何不行,緣由很簡單,由於這個函數只能設置某個 key
的 getter
setter
~。
symbol
proxy
reflect
這三劍客能作的事還有不少不少,這僅僅是實現 Object.observe
而已,還有更強大的功能能夠挖掘。
es6 真的很是強大,呼籲你們拋棄 ie11,擁抱美好的將來!
本文對你有幫助?歡迎掃碼加入前端學習小組微信羣: