Vue源碼探究-數據綁定邏輯架構
數據觀察系統是Vue實現數據綁定、異步更新的核心模塊,數據觀察系統的實現也是Vue源碼裏最爲複雜的部分,在仔細研究具體實現之前,先對整個數據綁定的邏輯架構進行一個充分的認識,會更有助於解讀源碼。
先說明一下,因爲三個類的名稱比較容易讓人誤解,所以在以後把Observer稱作觀察目標,Watcher稱作監視器,Dep稱作依賴對象。
數據綁定邏輯架構
Vue的數據觀察系統是基於發佈者/訂閱者模式,數據更新觸發刷新頁面的過程主要依賴數據觀察系統裏鐵三角關係。在這個系統中,主要角色分別是 Observer
、Dep
、Watcher
這三個對象,對於每一個角色在觀察數據更新的流程中各自承擔的職責需要深入進行理解。下面請出三個主角登場,來介紹一下它們:
Observer
Observer
相當於觀察目標類,在數據綁定邏輯架構中的職責是收集需要觀察的數據對象,進行變量存取器的包裝,並遞歸地對每一個需要觀察的對象註冊發佈者對象,再由發佈者去註冊相應的監視器。這裏非常巧妙的是觸發通知監視器數據更新的事件的註冊,一般的發佈訂閱模式需要建立一個事件管理器或者調度中心來統一管理各種事件的註冊,然而Vue的數據綁定不需要這樣的機制,它借用 Object.defineProperty
方法來爲每一個被監視的數據設置了存取器,依靠數據的存取行爲自然地實現了事件的觸發。在初始化Vue實例中設置的 data
屬性時,對這些輸入的數據對象對行了依賴追蹤,包裝後的變量存放在 _data
屬性中,這個過程中發佈者和監視器的依賴添加是不可見的;而通過配置 watch
屬性顯式設置的監視器,就可以在實例的 _watchers
私有屬性中查看到。每個組件初始化後有一個唯一的 _watcher
對象,它是一個用來監視在 data
中註冊的數據變動從而更新視圖的監視器,它也默認被添加到了各屬性的依賴監視數組中。在每個修改爲可觀察狀態的屬性中,都含有一個 Dep
實例即發佈者,這個對象的 subs
屬性就是用來存放依賴的所有監視器 Watcher
實例對象,subs
可以理解爲訂閱者,即所有訂閱了該數據對象變動的監視器的數組集合。之所以需要在一開始爲數據收集依賴,參考另一些開發者的總結是由於並非所有的數據都值得監視,要知道監視沒有用到的數據就是對性能的浪費,在實例觀察中也確實發現,頁面中沒有用到的屬性,沒有被初始化爲依賴項,這樣即便改變了它的數值,頁面也不會觸發多餘的刷新。
Dep
Dep
在Vue的數據觀察者系統裏充當發佈者的角色,它不僅用來觸發數據更新和建立依賴的事件,還用來存放每一個可監視數據所依賴的監視器,這個正是在第一步收集依賴時的重要一環。實例初始化的過程中收集了所有需要跟蹤變化的數據,在運用 Observer
重新包裝每一個屬性的同時,創建了各自的 dep
對象,並在get和set方法中分別使用了 Dep
的兩個方法:depend
建立依賴,notify
通知變動。另外 Dep
還負責維護依賴監視器的增減。在構造 Dep
類的過程中,定義了全局的 Dep.target
對象和 targetStack
數組,targetStack
數組是用來存放待執行的 watcher
棧,Dep.target
是用來指代當前的監視器,必須唯一,它的存在對於建立監視器的依賴起到重要作用,在重置數據的 getter
時,當它存在時才執行建立數據與監視器的依賴,即只有顯式配置了 watch
或創建了 computed
變量時纔會在實例的私有屬性裏看到監視器。
Watcher
Watcher
是這個架構中的監視器,充當觀察者的角色。在Vue實例初始化的過程中,一定會默認創建一個監視器,這個監視器就是用來監視實例對象的數據變化用來更新視圖的,實例的私有屬性 _watcher
用來存放它。在創建可觀察的數據時,每一個數據的 Dep
對象會收集監視器並建立依賴,當數據變化時,Dep
對象通知所有的監視器執行更新,執行更新有兩種模式,如果依賴是通過配置 computed
變量創建的,則會立即觸發相關的更新操作,如果數據的 dep.subs
數組中沒有依賴的監視器,則默認惰性更新模式。Watcher
類最主要的作用是通知視圖更新,衆所周知視圖的更新是非常花費時間,會影響程序性能,爲了儘量減少視圖更新導致的性能損失,在通知視圖執行更新操作之前會有一個緩衝時段,在這個時段中會收集最後一次監視器收到的變更,減少不必要的重複更新,實現最優性能。
架構圖示
充分了解了數據觀察系統的三個主角之後,再來看看官網貼出的示意圖,就會發現終於能摸清Vue的數據觀察系統的架構了,只不過渲染視圖的具體實現與數據觀察系統的交互暫時還沒有去摸索,以後會仔細地去探索,現在終於比較清晰地弄懂了Vue的數據綁定的原理了。
一個簡單的實例
爲了更清晰初步瞭解數據綁定相關的初始化過程,創建了一個非常簡單的實例,data配置了兩個屬性,其中 name
變量並不在頁面中使用,還顯式設置了一個依賴 msg
的監視器。
new Vue({
data () {
return {
msg: 'hello',
name: ''
}
},
watch: {
'msg' (value) {
console.log('msg更新了')
}
}
})
下面截圖是實例的相關監視器私有屬性,_watcher
是跟蹤頁面渲染的監視器,每個實例唯一;_wacthers
是實例所擁有的所有監視器的集合。顯式設置的 watcher
在是數組中的第一個對象。這裏雖然看不到 Observer
背後的包裝過程,但改變了 msg
屬性之後,可以看到監視器執行的回調顯示。
從Vue對象實例化着手到開始分析數據綁定的核心實現,這一路過來還沒有真正遇到值得困擾的問題。但未曾想到的是,數據綁定這個Vue的核心特色功能竟然讓我苦苦研讀了好幾天,似乎以前對於設計模式的瞭解顯得那樣無力。期間去搜索了一些前人做的分析說明文章以求從各個角度深入理解,但大多數解讀讀完後依然覺得沒能很透徹地理解這個模塊,後來讀到了一個簡易實現Vue觀察者系統的文章,讓我忽然對核心邏輯是如何實現的有了比較清晰的認識,而且對於設計模式也有了更深入的理解。也許第一次讀源碼的時候太多非核心的技術實現干擾了對於核心部分的理解,也因爲之前的一些知識不牢固,所以從這一次學習中得到了一個很好的經驗,要更加關注本質。