再次看了上次寫的博客關(guān)于Vue的MVVM,發(fā)現(xiàn)雖然介紹了MVVM的原理,但是感覺還不夠詳細(xì),現(xiàn)在就再次根據(jù)這篇博客寫詳細(xì)一點(diǎn),來(lái)看看new Vue的時(shí)候Vue究竟做了些什么事。
我想,以需求作為出發(fā)點(diǎn)來(lái)理解原理會(huì)比較容易,所以這篇博客會(huì)以提出需求 -> 解決需求的方式來(lái)寫。
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)聽器Watcher,然后還有一個(gè)Dep,過(guò)程是:
- 在
Compiler中監(jiān)聽數(shù)據(jù)的變化并綁定監(jiān)聽器; - 在觀察者
Observer中實(shí)現(xiàn)對(duì)所有數(shù)據(jù)的getter和setter; - 監(jiān)聽器
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ù)上面的的步驟描述,很容易感覺到Dep像是一個(gè)容器,存儲(chǔ)著對(duì)視圖的更新事件,是的,這是一個(gè)發(fā)布訂閱模式的實(shí)現(xiàn),該模式包含事件隊(duì)列subs,添加事件方法addSub,執(zhí)行事件隊(duì)列函數(shù)notify,移除事件隊(duì)列里的事件removeSub

看完發(fā)布訂閱模式后,繼續(xù)我們的流程。
添加依賴
-
在Compiler中監(jiān)聽數(shù)據(jù)的變化并綁定監(jiān)聽器
在上一篇對(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)聽器Watcher,因?yàn)楹竺嫘枰迅乱晥D的事件添加進(jìn)Dep中,所以Watcher中需要的參數(shù)要有一個(gè)更新事件也就是更新器updater中的視圖更新函數(shù),此外將當(dāng)前vm實(shí)例和得到的data鍵值也傳進(jìn)去備用;
compile中添加數(shù)據(jù)監(jiān)聽器
更新器
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)聽器
Watcher把更新事件添加進(jìn)Dep的事件隊(duì)列中;
這一步需要考慮一個(gè)問(wèn)題:在什么時(shí)候怎么樣把更新事件添加到Dep中去?
回顧上面所寫的,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)聽器),則觸發(fā)依賴添加事件depend;
image.png
③ 這時(shí)候的Dep.target就是被監(jiān)聽屬性的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
可見msg被引用了兩次就被監(jiān)聽了兩次,這時(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)聽的對(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)聽,這個(gè)也好解決,只要在
setter中進(jìn)行判斷即可,若為對(duì)象則針對(duì)該對(duì)象重新進(jìn)行監(jiān)聽
image.png
總結(jié)
到這里為止,我們就完成了view -< model的綁定,并且知道在new Vue的時(shí)候大致做了一些什么事了,剩下的就是逐步完善,例如對(duì)更多指令的支持,對(duì)methods以及computed和watch的支持。




























