最新 vue-next
的源碼發佈了,雖然是 pre-alpha
版本,但這時候實際上是閱讀源碼的比較好的時機。在 vue
中,比較重要的東西固然要數它的響應式系統,在以前的版本中,已經有若干篇文章對它的響應式原理和實現進行了介紹,這裏就不贅述了。在 vue-next
中,其實現原理和以前仍是相同的,即經過觀察者模式和數據劫持,只不過對其實現方式進行了改變。vue
對於解析原理的文章,我我的是比較喜歡那種「小白」風格的文章,即不要摘錄特別多的代碼,也不要闡述一些很深奧的原理與概念。在我剛接觸 react
的時候,還記得有一篇利用 jquery
來介紹 react
的文章,從簡入繁,面面俱到,其背後闡述的知識點對我後來學習 react
起到不少的幫助。react
所以,這篇文章我也打算按這種風格來寫一下利用最近空閒時間閱讀 vue-next
響應式模塊的源碼的一些心得與體會,算是拋磚引玉,同時實現一個極簡的響應式系統。jquery
若有錯誤,還望指正。git
不管是閱讀這篇文章,仍是閱讀 vue-next
響應式模塊的源碼,首先有兩個知識點是必備的:es6
Proxy
:es6 中新的代理內建工具類Reflect
:es6 中新的反射工具類因爲篇幅有限,這裏也不詳細贅述這兩個類的用途與使用方法了,推薦三篇我認爲不錯的文章,僅供參考:github
對於 vue-next
響應式系統的 RFC
,能夠參考[這裏](github.com/vuejs/rfcs/…。雖然距離如今有一段時間了,可是經過閱讀源碼,能夠發現一些影子。api
咱們大致要實現的效果以下面的代碼所示:數組
// 實現兩個方法 reactive 和 effect const state = reactive({ count: 0 }) effect(() => { console.log('count: ', state.count) }) state.count++ // 輸入 count: 1 複製代碼
能夠發現咱們熟悉的依賴收集階段(同時也是觀察者模式的訂閱過程),是在 effect
中進行的,依賴收集的準備工做(即數據劫持邏輯),是在 reactive
中進行的,而數據變化的觸發響應的邏輯在後面的 state.count++
代碼執行時進行(同時也是觀察者模式的發佈過程),以後便會執行以前傳入 effect
內部的回調函數並輸入 count: 1
。緩存
因爲 vue-next
用 ts
進行了重寫,這裏我也使用 ts
來實現這個極簡版本的響應式系統。主要涉及到的類型和公共變量以下:bash
type Effect = Function; type EffectMap = Map<string, Effect[]>; let currentEffect: Effect; const effectMap: EffectMap = new Map(); 複製代碼
currentEffect
:用來儲存當前正在收集依賴的 effect
effectMap
:表明目標對象每一個 key
所對應的依賴於它的 effect
數組,也能夠把它理解爲觀察者模式中的訂閱者字典在以前的版本中,vue
利用 Object.defineProperty
中的 setter
和 getter
來對數據對象進行劫持,vue-next
則經過 Proxy
。衆所周知,Object.defineProperty
所實現的數據劫持是有必定限制的,而 Proxy
就會強大不少。
首先,咱們在腦後中,設想一下如何使用 Proxy
來實現數據劫持呢?很簡單,大致結構以下所示:
export function reactive(obj) { const proxied = new Proxy(obj, handlers); return proxied; } 複製代碼
這裏的 handlers
是聲明如何處理各個 trap
的邏輯,好比:
const handlers = { get: function(target, key, receiver) { ... }, set: function(target, key, value, receiver) { ... }, deleteProperty(target, key) { ... } // ...以及其餘 trap } 複製代碼
因爲這裏是極簡版本的實現,那麼咱們就僅僅實現 get
和 set
兩個 trap
就能夠了,分別對應依賴收集和觸發響應的邏輯。
對於依賴收集的實現,因爲是極簡版本,實現的前提以下:
哈哈,基本這四點排除以後,這個依賴收集函數就會很輕很薄,以下:
function(target, key: string, receiver) { // 僅僅在某個 effect 內部進行依賴收集 if (currentEffect) { if (effectMap.has(key)) { const effects = effectMap.get(key); if (effects.indexOf(currentEffect) === -1) { effects.push(currentEffect); } } else { effectMap.set(key, [currentEffect]); } } return Reflect.get(target, key, receiver); } 複製代碼
實現的邏輯很簡單,其實就是觀察者模式中註冊訂閱者的實現邏輯,值得注意的是,這裏對於 target
的賦值邏輯,咱們委託給 Reflect
來完成,雖然 target[key]
也是能夠工做的,可是使用 Reflect
是更提倡的方式。
觸發響應的邏輯就比較簡單了,實際上是對應觀察者模式中,發佈事件的邏輯,以下:
function(target, key: string, value, receiver) { const result = Reflect.set(target, key, value, receiver); if (effectMap.has(key)) { effectMap.get(key).forEach(effect => effect()); } return result; } 複製代碼
一樣,這裏使用 Reflect
來對 target
進行賦值操做,由於它會返回一個 boolean
值表明是否成功,而 set
這個 trap
也須要表明相同含義的值。
實現了數據劫持的代理邏輯以後,咱們只須要在 reactive
這個方法中,返回一個代理對象的實例便可,還記的上文中咱們在實現以前腦海中浮現的大體代碼框架嗎?
以下:
export function reactive(obj: any) { const proxied = new Proxy(obj, { get: function(target, key: string, receiver) { if (currentEffect) { if (effectMap.has(key)) { const effects = effectMap.get(key); if (effects.indexOf(currentEffect) === -1) { effects.push(currentEffect); } } else { effectMap.set(key, [currentEffect]); } } return Reflect.get(target, key, receiver); }, set: function(target, key: string, value, receiver) { const result = Reflect.set(target, key, value, receiver); if (effectMap.has(key)) { effectMap.get(key).forEach(effect => effect()); } return result; } }); return proxied; } 複製代碼
上文中提到了,對於依賴收集的工做,咱們是有條件地進行的,即在一個 effect
中,咱們纔會進行收集,其餘狀況下的取值邏輯,咱們則不會進行依賴收集,所以,effect
方法正式爲了實現這點而存在的,以下:
export function effect(fn: Function) { const effected = function() { fn(); }; currentEffect = effected; effected(); currentEffect = undefined; return effected; } 複製代碼
之因此實現如此簡單,是由於咱們這裏是極簡版本,不須要考慮諸如 readOnly
、異常以及收集時機等因素。能夠發現,就是將傳入的回調函數包裹在另外一個方法中,而後將這個方法用 currentEffect
這個變量暫存,以後嘗試運行一下便可。當 effect
運行完畢以後,再將 currentEffect
置空,這樣就能夠達到只在 effect
下進行依賴收集的目的。
我在 codepen
上簡單寫了一個計數器 demo
,連接以下: codepen.io/littlelyon1…
這個極簡的響應式系統雖然能用,可是有不少未考慮的因素,其實就是在上文中被咱們忽略的那些前提條件,這裏再列舉一下,並給出源代碼中的解法:
ref
對象,其 value
指向基礎數據類型的值trap
處理邏輯但我仍然推薦你直接去閱讀一下源碼,由於你會發現,源碼會在這個極簡版本基礎上,利用了更加複雜數據結構以及流程,來控制依賴收集和觸發響應的流程,同時各類特殊狀況也有更加明細的考慮。
另外,這僅僅是 vue-next
響應式系統的簡易實現,諸如其餘功能模塊,好比指令、模板解析、vdom
等,我也準備利用最近的空閒時間再去看看,有時間的話,最近也整理出來,分享給你們。
關注公衆號全棧_101,只談技術,不談人生。
長期兼職接各類規模的外包項目,有意者私聊。