再次看了上次寫(xiě)的博客關(guān)于Vue的MVVM,發(fā)現(xiàn)雖然介紹了MVVM的原理,但是感覺(jué)還不夠詳細(xì)癞己,現(xiàn)在就再次根據(jù)這篇博客寫(xiě)詳細(xì)一點(diǎn)案狠,來(lái)看看new Vue
的時(shí)候Vue究竟做了些什么事总滩。
我想纯蛾,以需求作為出發(fā)點(diǎn)來(lái)理解原理會(huì)比較容易舶沛,所以這篇博客會(huì)以提出需求 -> 解決需求的方式來(lái)寫(xiě)冠王。
Vue中的MVVM原理介紹
可以先閱讀我的這篇博客了解一下關(guān)于Vue的MVVM舌镶,另外需要記住這一幅圖(很重要)柱彻,這張圖就是本篇博客的概括:
回顧
繼上一篇文章Vue中的MVVM--model -> view的綁定,我們完成了對(duì)頁(yè)面的初始化渲染餐胀,達(dá)成了如下要求:
但還未完成
view -> model
的綁定否灾,所以不能通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)視圖的更新卖擅,今天就來(lái)完成剩余部分
提出需求
繼上圖:
我們需要做到的是:當(dāng)修改輸入框的數(shù)據(jù)時(shí),上面的文字也隨之進(jìn)行刷新墨技。
分析
先來(lái)看看還未完成的部分:
其中包含觀察者
Observer
断楷,監(jiān)聽(tīng)器Watcher
,然后還有一個(gè)Dep
崭别,過(guò)程是:
- 在
Compiler
中監(jiān)聽(tīng)數(shù)據(jù)的變化并綁定監(jiān)聽(tīng)器冬筒; - 在觀察者
Observer
中實(shí)現(xiàn)對(duì)所有數(shù)據(jù)的getter
和setter
恐锣; - 監(jiān)聽(tīng)器
Watcher
把更新事件添加進(jìn)Dep
的事件隊(duì)列中; - 觀察者
Observer
發(fā)現(xiàn)數(shù)據(jù)產(chǎn)生變化的時(shí)候通知Dep
舞痰; -
Dep
把事件隊(duì)列中的更新事件全部執(zhí)行一遍土榴;
總結(jié)下來(lái)就是實(shí)現(xiàn)兩個(gè)事情:
- 添加數(shù)據(jù)依賴;
- 觸發(fā)數(shù)據(jù)變更事件响牛;
接下來(lái)就先創(chuàng)建Watcher
玷禽,Observer
,Dep
三個(gè)類呀打;
image.png
image.png
image.png
先來(lái)看看Dep
是什么
根據(jù)上面的的步驟描述论衍,很容易感覺(jué)到Dep
像是一個(gè)容器,存儲(chǔ)著對(duì)視圖的更新事件聚磺,是的,這是一個(gè)發(fā)布訂閱模式的實(shí)現(xiàn)炬丸,該模式包含事件隊(duì)列subs
瘫寝,添加事件方法addSub
,執(zhí)行事件隊(duì)列函數(shù)notify
稠炬,移除事件隊(duì)列里的事件removeSub
看完發(fā)布訂閱模式后焕阿,繼續(xù)我們的流程。
添加依賴
-
在Compiler中監(jiān)聽(tīng)數(shù)據(jù)的變化并綁定監(jiān)聽(tīng)器
在上一篇對(duì)model -> view
的綁定中我們有一個(gè)針對(duì)數(shù)據(jù)和指令統(tǒng)一進(jìn)行綁定的方法bind
首启,為了不和后面的v-bind
指令沖突暮屡,現(xiàn)在改為了bindData
;
image.png
在這個(gè)函數(shù)承載的功能有獲取tag文本和執(zhí)行視圖的更新,所以我們可以在這個(gè)函數(shù)中添加對(duì)數(shù)據(jù)的監(jiān)聽(tīng)器Watcher
毅桃,因?yàn)楹竺嫘枰迅乱晥D的事件添加進(jìn)Dep
中褒纲,所以Watcher
中需要的參數(shù)要有一個(gè)更新事件也就是更新器updater
中的視圖更新函數(shù),此外將當(dāng)前vm實(shí)例和得到的data
鍵值也傳進(jìn)去備用;
compile中添加數(shù)據(jù)監(jiān)聽(tīng)器
更新器
watcher
接著 -
在觀察者Observer中實(shí)現(xiàn)對(duì)所有數(shù)據(jù)的getter和setter
注意這一步需要考慮到數(shù)據(jù)中含有嵌套的對(duì)象钥飞,需要進(jìn)行遞歸操作才能全部添加getter
和setter
莺掠,使用的是Object.definedProperty
,Observer
接受的參數(shù)是data
對(duì)象:
image.png
然后在MVVM
類中代入data
并執(zhí)行observer
:
image.png
接著對(duì)所有data
中的屬性綁定getter
和setter
读宙,這一步需要進(jìn)行遞歸操作:
image.png
最后回到Compiler
中彻秆,實(shí)現(xiàn)視圖對(duì)數(shù)據(jù)的修改:
比如在輸入框中修改數(shù)據(jù)直接反應(yīng)到data
上
image.png
image.png
來(lái)看看成果:
image.png
這個(gè)時(shí)候在視圖上對(duì)數(shù)據(jù)進(jìn)行的修改就可以反映到data
上,并觸發(fā)該數(shù)據(jù)的setter
函數(shù)结闸; -
監(jiān)聽(tīng)器
Watcher
把更新事件添加進(jìn)Dep
的事件隊(duì)列中唇兑;
這一步需要考慮一個(gè)問(wèn)題:在什么時(shí)候怎么樣把更新事件添加到Dep
中去?
回顧上面所寫(xiě)的,data
中的每一個(gè)屬性都有一個(gè)對(duì)應(yīng)的Watcher
桦锄,可以在Watcher
中獲取得到對(duì)應(yīng)的data
中的屬性扎附。那么在這個(gè)獲取的過(guò)程中,又會(huì)觸發(fā)該屬性的getter
结耀,就可以考慮在該屬性的getter
中添加帕棉,分解成一下步驟就是:
① 把這個(gè)Watcher
通過(guò)構(gòu)造函數(shù)本身的屬性target保留在Dep
中针肥,然后去data
中取值;
image.png
② 取值的時(shí)候觸發(fā)Observer
中該屬性的getter
香伴,在Observer
中new一個(gè)Dep
實(shí)例出來(lái)慰枕,判斷如果Dep類的target
非空(也就是該屬性已被有監(jiān)聽(tīng)器),則觸發(fā)依賴添加事件depend
;
image.png
③ 這時(shí)候的Dep.target
就是被監(jiān)聽(tīng)屬性的Watcher
即纲,在Dep類中添加一個(gè)方法depend
具帮,用來(lái)把該屬性的Watcher
添加進(jìn)事件隊(duì)列subs
中,但是這一步要當(dāng)前的Watcher
低斋,需要在Watcher類中進(jìn)行觸發(fā)蜂厅,所以在Watcher
中創(chuàng)建一個(gè)函數(shù)addDep
,把Dep
的實(shí)例作為參數(shù)放進(jìn)去膊畴,然后在addDep
中進(jìn)行更新事件的添加:
image.png
image.png
然后置空Dep.target
掘猿,用于下一個(gè)數(shù)據(jù)的依賴添加
image.png
現(xiàn)在我們來(lái)看看subs
中有些什么
image.png
可見(jiàn)msg
被引用了兩次就被監(jiān)聽(tīng)了兩次,這時(shí)候只要當(dāng)msg
這個(gè)數(shù)據(jù)發(fā)生變化并觸發(fā)setter
時(shí)唇跨,將subs
中所有的watcher
實(shí)例里的更新回調(diào)update
拉出來(lái)執(zhí)行即可 更新視圖
- 更新視圖的時(shí)候稠通,我們先要獲取當(dāng)前的數(shù)據(jù)新值,然后作為參數(shù)放進(jìn)回調(diào)函數(shù)中买猖,并且還要對(duì)新的數(shù)據(jù)進(jìn)行上面的依賴添加步驟改橘,那么
Watcher
還需要一個(gè)update
函數(shù)用來(lái)統(tǒng)一做這個(gè)事:
image.png - 在
Observer
的setter
中觸發(fā)Dep
的notify
方法,進(jìn)行視圖的更新:
image.png -
到了這步其實(shí)就已經(jīng)達(dá)成效果了:
image.png
- 修復(fù)bug
雖然MVVM雙向綁定的功能已經(jīng)達(dá)成玉控,但是還是有不少bug的飞主,其中最嚴(yán)重的有兩個(gè)
-
當(dāng)我們多次更新數(shù)據(jù)的時(shí)候,會(huì)發(fā)現(xiàn)添加進(jìn)
subs
的watcher
發(fā)生了遞增的現(xiàn)象高诺,所以當(dāng)快速更新數(shù)據(jù)時(shí)就會(huì)導(dǎo)致執(zhí)行函數(shù)過(guò)多而頁(yè)面崩潰碌识;
image.png
造成這個(gè)現(xiàn)象的原因是在進(jìn)行第一次的更新時(shí),watcher
將同一個(gè)數(shù)據(jù)的新值也進(jìn)行了依賴添加虱而,也就是let newVal = this.get()
這一段丸冕;
image.png
既然知道了原因,那么解決起來(lái)也很簡(jiǎn)單薛窥,給每一個(gè)被監(jiān)聽(tīng)的對(duì)象都添加一個(gè)id即可胖烛。
因?yàn)樘砑觭ub的操作是在Watcher
中進(jìn)行的,所以在Watcher
中創(chuàng)建一個(gè)對(duì)象depIds
image.png
然后給每一個(gè)Dep
都添加一個(gè)不同的id
image.png
最后在Watcher
中判斷depIds
是否已經(jīng)有這個(gè)id的Dep實(shí)例存在诅迷,如果沒(méi)有則添加進(jìn)去并執(zhí)行addSub
佩番,否則不執(zhí)行:
image.png
效果,無(wú)論怎么修改罢杉,都只會(huì)有固定數(shù)量的Watcher
存在:
image.png -
當(dāng)修改數(shù)據(jù)為對(duì)象的時(shí)候趟畏,這個(gè)對(duì)象沒(méi)有進(jìn)行監(jiān)聽(tīng),這個(gè)也好解決滩租,只要在
setter
中進(jìn)行判斷即可赋秀,若為對(duì)象則針對(duì)該對(duì)象重新進(jìn)行監(jiān)聽(tīng)
image.png
總結(jié)
到這里為止利朵,我們就完成了view -< model
的綁定,并且知道在new Vue
的時(shí)候大致做了一些什么事了猎莲,剩下的就是逐步完善绍弟,例如對(duì)更多指令的支持,對(duì)methods
以及computed
和watch
的支持著洼。