回顧目標(biāo)
數(shù)據(jù)驅(qū)動視圖:
- 理解watcher玄括、dep、observer這三個對象之間的關(guān)系
- 這和VUE對象又有什么關(guān)系盆顾?
- 這和視圖又有什么關(guān)系锥惋?
敘述過程
先徹底理解了一下VUE的簡介,并寫出了一份建議書褂删。關(guān)鍵詞有漸進式飞醉、自底向上逐層應(yīng)用、聲明式開發(fā)屯阀、組件化缅帘。
之后了解了觀察者模式,觀察者模式的初衷是建立低耦合的通信機制难衰,定義對象間的一種一對多的依賴關(guān)系股毫,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新召衔。
記得以前看過一本掘金小冊铃诬,其中作者的結(jié)論是:每個vue對象對應(yīng)一個watcher,每個observer和watcher通過dep建立一對多關(guān)系苍凛。
然后開始走讀代碼趣席。大概讀了一遍watcher、observer醇蝴、deb發(fā)現(xiàn)沒什么新的收獲宣肚,開始認(rèn)為每一個vue對象管理一個watcher,然后每個變量都管理一個observer,每個observer管理一個sub悠栓,由observer通知watcher霉涨,然后渲染按价。
但是提出了一個關(guān)鍵性的問題,有一個公共的target是用來存放watcher的笙瑟,發(fā)現(xiàn)這個target和依賴關(guān)系非常有關(guān)楼镐,然后啟動瀏覽器用檢查器監(jiān)察這個值,發(fā)現(xiàn)這個值一般是null往枷,然后打開vscode框产,全局查找,找出是什么時候?qū)懙倪@個值错洁,最后定位這個target在watch讀數(shù)據(jù)*的時候被緩存秉宿。
這段話非常的重要,只有當(dāng)執(zhí)行用戶自定義函數(shù)的時候屯碴,才會建立依賴關(guān)系描睦。
翻看了文檔。找到了一段清晰的代碼导而,
var vm = new Vue({
data: { a: 1 },
computed: {
// 僅讀取
aDouble: function () {
return this.a * 2
},
// 讀取和設(shè)置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
然后帶著上面的問題酌摇,去閱讀了vue的構(gòu)造過程,在initState中發(fā)現(xiàn)了computed的構(gòu)造,其中有一個關(guān)鍵的用法:
function initComputed (vm: Component) {
const computed = vm.$options.computed
...
const userDef = computed[key]
makeComputedGetter(userDef, vm)
...
}
function makeComputedGetter (getter: Function
, owner: Component): Function {
const watcher = new Watcher(owner, getter, noop, {
lazy: true
})
...
}
對于觀察者來說用戶定義的函數(shù)是getter嗡载,也就是對于框架底層來說窑多,程序員寫的computed函數(shù),對組件來說是getter洼滚。computed實際上是用包裝了一下用戶定義的函數(shù)埂息。可以把他理解成一種特殊的watcher遥巴,根據(jù)官方的文檔千康,提到了computed有緩存功能,不會更新相同的結(jié)果铲掐。簡單起見我們就把computed當(dāng)作watcher理解拾弃。
然后開始關(guān)注這個函數(shù)內(nèi)部發(fā)生了什么:
...
computed: {
// 僅讀取
aDouble: function () {
return this.a * 2
},
...
這里讀取了this.a。我想起來vue內(nèi)部的變量都是處理過的摆霉。用了observe這個工廠方法加工過豪椿,set和get都和watcher和deb耦合。執(zhí)行aDouble這個函數(shù),computed就作為一個watcher被Dep.target記住了携栋,而a就是一個obsserver,調(diào)用了get就會將watcher保存在deps數(shù)組中,就好像a調(diào)用get就被aDouble盯上(watch)了,下次a變化(調(diào)用a.set)就重新執(zhí)行一遍aDouble搭盾。
這個時候我們再回去看wacher對象,發(fā)現(xiàn)這個對象有一個cb存放回調(diào)函數(shù)婉支,而且代碼中還出來了屬于VNode模塊的patch鸯隅。cb應(yīng)該是負(fù)責(zé)重新渲染視圖。
所以這就可以解釋VUE是如何驅(qū)動視圖改變的向挖,源自于一種自動更新的一對多機制蝌以。這可以解決父子組件的通信問題炕舵,父子組件狀態(tài)同步可以通過向子組件注入父組件的數(shù)據(jù)引用,可以實現(xiàn)單向數(shù)據(jù)流跟畅。
查看了官方文檔咽筋,結(jié)果發(fā)現(xiàn):
每個組件實例都對應(yīng)一個 watcher 實例,它會在組件渲染的過程中把“接觸”過的數(shù)據(jù)屬性記錄為依賴碍彭。之后當(dāng)依賴項的 setter 觸發(fā)時晤硕,會通知 watcher悼潭,從而使它關(guān)聯(lián)的組件重新渲染庇忌。
證實了掘金那個作者說的沒錯,每一個vue的確有一個watcher
查看watcher.get的call stack:
get (vue.js:647)
Watcher (vue.js:638)
Vue.$watch (vue.js:1254)
createWatcher (vue.js:1232)
initWatch (vue.js:1217)
initState (vue.js:1105)
Vue._init (vue.js:2110)
Vue (vue.js:2150)
(anonymous) (app.js:7)
我們看到Vue.$watch說明vue下確實有一個watcher
這個watcher目的是什么呢舰褪?我們回去看了一下watch的構(gòu)造函數(shù)
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object = {}
) {
...
this.getter = parsePath(expOrFn)
this.value = this.lazy
? undefined
: this.get()
...
}
在parsePath之中會觸發(fā)所有vue的內(nèi)部對象的get的函數(shù)句柄皆疹,然后被執(zhí)行,所有的內(nèi)部數(shù)據(jù)改動都會導(dǎo)致wacher重新渲染數(shù)據(jù)
評估結(jié)果:
結(jié)果不等于目標(biāo)
- 開始否認(rèn)了認(rèn)為每一個vue對象管理一個watcher
- 我們推導(dǎo)出了computed和watcher的關(guān)系,并認(rèn)為這是watch唯一的作用
結(jié)果等于目標(biāo)
- 理解watcher占拍、dep略就、observer這三個對象之間的關(guān)系
每一個用戶定義回調(diào)函數(shù)管理一個watcher,然后每個VUE內(nèi)部data都管理一個observer,每個observer管理一個sub晃酒,由observer通知watcher表牢。 - 這和VUE對象又有什么關(guān)系
VUE對象保存了data數(shù)據(jù),每一個data都管理一個observer贝次,computed是一種特殊的watcher,VUE對象渲染視圖的時候會要求watcher返回一個值崔兴,這個時候watcher執(zhí)行用戶定義的函數(shù)句柄,也就是computed當(dāng)中定義的函數(shù)時蛔翅,每個data被讀取的時候會和當(dāng)前的函數(shù)建立依賴關(guān)系敲茄,當(dāng)這個data更新的時候會重新渲染視圖,重新執(zhí)行用戶定義函數(shù)山析。且當(dāng)new一個vuew對象的時候watcher會監(jiān)聽所有的內(nèi)部數(shù)據(jù)對象堰燎。 - 這和視圖又有什么關(guān)系
每個watcher負(fù)責(zé)自動更新視圖。
分析原因
開始否認(rèn)為每一個vue對象管理一個watcher
- 先看了別人的文章然后帶著結(jié)論去看的代碼笋轨,最后只是找怎么支持這個結(jié)論的代碼段秆剪。但是發(fā)現(xiàn)了文章之外的內(nèi)容誤以為文章錯誤了。
- watcher是被vue對象直接調(diào)用的爵政,很容易讓人聯(lián)想一對一關(guān)系鸟款,但不確定
- dep.target作為關(guān)鍵的變量,作搞清楚這個變量的作用茂卦,就可以回答誰依賴誰這個問題何什。
我們推導(dǎo)出了computed和watcher的關(guān)系,并認(rèn)為這是唯一的作用
- 當(dāng)看到watcher.value時等龙。意識到一個vue對象可能需要維護多個watcher处渣,因此原來的假設(shè)不成立伶贰,那么需要找到watcher具體是做什么的。沿著watcher.get一直往下看罐栈,發(fā)現(xiàn)了watcher的作用和computed有關(guān)聯(lián)黍衙,因此認(rèn)為computed是watcher的一種表現(xiàn),而一個vue對象又有n個computed荠诬。這才認(rèn)為作者的觀點是錯誤的琅翻。
- 很多文章中沒有找到關(guān)于computed和watcher的作用,誤以為是別人理解錯了柑贞,其實這和我的結(jié)論并不沖突方椎。
推演規(guī)律
自然語言更加容易被人理解,因為首因效應(yīng)钧嘶,人對事物的理解不太容易改變棠众,那么我們看別人的文章,我們理解的不是原作者的設(shè)計思想有决,而是經(jīng)過了非原作者的幾層解釋的最終結(jié)果闸拿。
那么這就導(dǎo)致了我對其他人的結(jié)論有一種懷疑,想找出反證的點书幕。而這個時候就需要從官方文檔中找到依據(jù)新荤,作者和團隊的文檔最具有權(quán)威和可行度。
從非自然語言——代碼台汇】凉牵總結(jié)出來的,我們可以在閱讀源代碼參考別人的文章励七,但是其中最有價值的部分智袭,不是別人的結(jié)論,而是別人閱讀的順序掠抬,看別人是如何抓住主干去理解的吼野。綜上有兩個東西對閱讀源代碼有益,分別是文章的行文順序和文章的大標(biāo)題两波,文章的大標(biāo)題一般是對源代碼模塊的高度概括瞳步。