Vue源碼探究-數(shù)據(jù)綁定邏輯架構(gòu)
數(shù)據(jù)觀察系統(tǒng)是Vue實現(xiàn)數(shù)據(jù)綁定挪拟、異步更新的核心模塊挨务,數(shù)據(jù)觀察系統(tǒng)的實現(xiàn)也是Vue源碼里最為復(fù)雜的部分,在仔細(xì)研究具體實現(xiàn)之前舞丛,先對整個數(shù)據(jù)綁定的邏輯架構(gòu)進(jìn)行一個充分的認(rèn)識耘子,會更有助于解讀源碼。
先說明一下球切,因為三個類的名稱比較容易讓人誤解,所以在以后把Observer稱作觀察目標(biāo)绒障,Watcher稱作監(jiān)視器吨凑,Dep稱作依賴對象。
數(shù)據(jù)綁定邏輯架構(gòu)
Vue的數(shù)據(jù)觀察系統(tǒng)是基于發(fā)布者/訂閱者模式户辱,數(shù)據(jù)更新觸發(fā)刷新頁面的過程主要依賴數(shù)據(jù)觀察系統(tǒng)里鐵三角關(guān)系鸵钝。在這個系統(tǒng)中,主要角色分別是 Observer
庐镐、Dep
恩商、Watcher
這三個對象,對于每一個角色在觀察數(shù)據(jù)更新的流程中各自承擔(dān)的職責(zé)需要深入進(jìn)行理解必逆。下面請出三個主角登場怠堪,來介紹一下它們:
Observer
Observer
相當(dāng)于觀察目標(biāo)類,在數(shù)據(jù)綁定邏輯架構(gòu)中的職責(zé)是收集需要觀察的數(shù)據(jù)對象名眉,進(jìn)行變量存取器的包裝粟矿,并遞歸地對每一個需要觀察的對象注冊發(fā)布者對象,再由發(fā)布者去注冊相應(yīng)的監(jiān)視器损拢。這里非常巧妙的是觸發(fā)通知監(jiān)視器數(shù)據(jù)更新的事件的注冊陌粹,一般的發(fā)布訂閱模式需要建立一個事件管理器或者調(diào)度中心來統(tǒng)一管理各種事件的注冊,然而Vue的數(shù)據(jù)綁定不需要這樣的機(jī)制福压,它借用 Object.defineProperty
方法來為每一個被監(jiān)視的數(shù)據(jù)設(shè)置了存取器掏秩,依靠數(shù)據(jù)的存取行為自然地實現(xiàn)了事件的觸發(fā)。在初始化Vue實例中設(shè)置的 data
屬性時荆姆,對這些輸入的數(shù)據(jù)對象對行了依賴追蹤蒙幻,包裝后的變量存放在 _data
屬性中,這個過程中發(fā)布者和監(jiān)視器的依賴添加是不可見的胞枕;而通過配置 watch
屬性顯式設(shè)置的監(jiān)視器杆煞,就可以在實例的 _watchers
私有屬性中查看到。每個組件初始化后有一個唯一的 _watcher
對象,它是一個用來監(jiān)視在 data
中注冊的數(shù)據(jù)變動從而更新視圖的監(jiān)視器决乎,它也默認(rèn)被添加到了各屬性的依賴監(jiān)視數(shù)組中队询。在每個修改為可觀察狀態(tài)的屬性中,都含有一個 Dep
實例即發(fā)布者构诚,這個對象的 subs
屬性就是用來存放依賴的所有監(jiān)視器 Watcher
實例對象蚌斩,subs
可以理解為訂閱者,即所有訂閱了該數(shù)據(jù)對象變動的監(jiān)視器的數(shù)組集合范嘱。之所以需要在一開始為數(shù)據(jù)收集依賴送膳,參考另一些開發(fā)者的總結(jié)是由于并非所有的數(shù)據(jù)都值得監(jiān)視,要知道監(jiān)視沒有用到的數(shù)據(jù)就是對性能的浪費丑蛤,在實例觀察中也確實發(fā)現(xiàn)叠聋,頁面中沒有用到的屬性,沒有被初始化為依賴項受裹,這樣即便改變了它的數(shù)值碌补,頁面也不會觸發(fā)多余的刷新。
Dep
Dep
在Vue的數(shù)據(jù)觀察者系統(tǒng)里充當(dāng)發(fā)布者的角色棉饶,它不僅用來觸發(fā)數(shù)據(jù)更新和建立依賴的事件厦章,還用來存放每一個可監(jiān)視數(shù)據(jù)所依賴的監(jiān)視器,這個正是在第一步收集依賴時的重要一環(huán)照藻。實例初始化的過程中收集了所有需要跟蹤變化的數(shù)據(jù)袜啃,在運用 Observer
重新包裝每一個屬性的同時,創(chuàng)建了各自的 dep
對象幸缕,并在get和set方法中分別使用了 Dep
的兩個方法:depend
建立依賴群发,notify
通知變動。另外 Dep
還負(fù)責(zé)維護(hù)依賴監(jiān)視器的增減冀值。在構(gòu)造 Dep
類的過程中也物,定義了全局的 Dep.target
對象和 targetStack
數(shù)組,targetStack
數(shù)組是用來存放待執(zhí)行的 watcher
棧列疗,Dep.target
是用來指代當(dāng)前的監(jiān)視器滑蚯,必須唯一,它的存在對于建立監(jiān)視器的依賴起到重要作用抵栈,在重置數(shù)據(jù)的 getter
時告材,當(dāng)它存在時才執(zhí)行建立數(shù)據(jù)與監(jiān)視器的依賴,即只有顯式配置了 watch
或創(chuàng)建了 computed
變量時才會在實例的私有屬性里看到監(jiān)視器古劲。
Watcher
Watcher
是這個架構(gòu)中的監(jiān)視器斥赋,充當(dāng)觀察者的角色。在Vue實例初始化的過程中产艾,一定會默認(rèn)創(chuàng)建一個監(jiān)視器疤剑,這個監(jiān)視器就是用來監(jiān)視實例對象的數(shù)據(jù)變化用來更新視圖的滑绒,實例的私有屬性 _watcher
用來存放它。在創(chuàng)建可觀察的數(shù)據(jù)時隘膘,每一個數(shù)據(jù)的 Dep
對象會收集監(jiān)視器并建立依賴疑故,當(dāng)數(shù)據(jù)變化時,Dep
對象通知所有的監(jiān)視器執(zhí)行更新弯菊,執(zhí)行更新有兩種模式纵势,如果依賴是通過配置 computed
變量創(chuàng)建的,則會立即觸發(fā)相關(guān)的更新操作管钳,如果數(shù)據(jù)的 dep.subs
數(shù)組中沒有依賴的監(jiān)視器钦铁,則默認(rèn)惰性更新模式。Watcher
類最主要的作用是通知視圖更新才漆,眾所周知視圖的更新是非撑2埽花費時間,會影響程序性能醇滥,為了盡量減少視圖更新導(dǎo)致的性能損失躏仇,在通知視圖執(zhí)行更新操作之前會有一個緩沖時段,在這個時段中會收集最后一次監(jiān)視器收到的變更腺办,減少不必要的重復(fù)更新,實現(xiàn)最優(yōu)性能糟描。
架構(gòu)圖示
充分了解了數(shù)據(jù)觀察系統(tǒng)的三個主角之后怀喉,再來看看官網(wǎng)貼出的示意圖,就會發(fā)現(xiàn)終于能摸清Vue的數(shù)據(jù)觀察系統(tǒng)的架構(gòu)了船响,只不過渲染視圖的具體實現(xiàn)與數(shù)據(jù)觀察系統(tǒng)的交互暫時還沒有去摸索躬拢,以后會仔細(xì)地去探索,現(xiàn)在終于比較清晰地弄懂了Vue的數(shù)據(jù)綁定的原理了见间。
一個簡單的實例
為了更清晰初步了解數(shù)據(jù)綁定相關(guān)的初始化過程聊闯,創(chuàng)建了一個非常簡單的實例,data配置了兩個屬性米诉,其中 name
變量并不在頁面中使用菱蔬,還顯式設(shè)置了一個依賴 msg
的監(jiān)視器。
new Vue({
data () {
return {
msg: 'hello',
name: ''
}
},
watch: {
'msg' (value) {
console.log('msg更新了')
}
}
})
下面截圖是實例的相關(guān)監(jiān)視器私有屬性史侣,_watcher
是跟蹤頁面渲染的監(jiān)視器拴泌,每個實例唯一;_wacthers
是實例所擁有的所有監(jiān)視器的集合惊橱。顯式設(shè)置的 watcher
在是數(shù)組中的第一個對象蚪腐。這里雖然看不到 Observer
背后的包裝過程,但改變了 msg
屬性之后税朴,可以看到監(jiān)視器執(zhí)行的回調(diào)顯示回季。
從Vue對象實例化著手到開始分析數(shù)據(jù)綁定的核心實現(xiàn)家制,這一路過來還沒有真正遇到值得困擾的問題。但未曾想到的是泡一,數(shù)據(jù)綁定這個Vue的核心特色功能竟然讓我苦苦研讀了好幾天颤殴,似乎以前對于設(shè)計模式的了解顯得那樣無力。期間去搜索了一些前人做的分析說明文章以求從各個角度深入理解瘾杭,但大多數(shù)解讀讀完后依然覺得沒能很透徹地理解這個模塊诅病,后來讀到了一個簡易實現(xiàn)Vue觀察者系統(tǒng)的文章,讓我忽然對核心邏輯是如何實現(xiàn)的有了比較清晰的認(rèn)識粥烁,而且對于設(shè)計模式也有了更深入的理解贤笆。也許第一次讀源碼的時候太多非核心的技術(shù)實現(xiàn)干擾了對于核心部分的理解,也因為之前的一些知識不牢固讨阻,所以從這一次學(xué)習(xí)中得到了一個很好的經(jīng)驗芥永,要更加關(guān)注本質(zhì)。