Vue中的MVVM(2)--view -> model的綁定

再次看了上次寫的博客關(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)寫。

項(xiàng)目地址,歡迎start

Vue中的MVVM原理介紹

可以先閱讀我的這篇博客了解一下關(guān)于Vue的MVVM,另外需要記住這一幅圖(很重要),這張圖就是本篇博客的概括:

image.png

回顧

繼上一篇文章Vue中的MVVM--model -> view的綁定,我們完成了對(duì)頁(yè)面的初始化渲染,達(dá)成了如下要求:

image.png

image.png

但還未完成view -> model的綁定,所以不能通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)視圖的更新,今天就來(lái)完成剩余部分

提出需求

繼上圖:


image.png

我們需要做到的是:當(dāng)修改輸入框的數(shù)據(jù)時(shí),上面的文字也隨之進(jìn)行刷新。

分析

先來(lái)看看還未完成的部分:

image.png

其中包含觀察者Observer,監(jiān)聽器Watcher,然后還有一個(gè)Dep,過(guò)程是:

  1. Compiler中監(jiān)聽數(shù)據(jù)的變化并綁定監(jiān)聽器;
  2. 在觀察者Observer中實(shí)現(xiàn)對(duì)所有數(shù)據(jù)的gettersetter;
  3. 監(jiān)聽器Watcher把更新事件添加進(jìn)Dep的事件隊(duì)列中;
  4. 觀察者Observer發(fā)現(xiàn)數(shù)據(jù)產(chǎn)生變化的時(shí)候通知Dep
  5. Dep把事件隊(duì)列中的更新事件全部執(zhí)行一遍;

總結(jié)下來(lái)就是實(shí)現(xiàn)兩個(gè)事情:

  1. 添加數(shù)據(jù)依賴;
  2. 觸發(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

image.png

看完發(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)行遞歸操作才能全部添加gettersetter,使用的是Object.definedProperty,Observer接受的參數(shù)是data對(duì)象:

    image.png

    然后在MVVM類中代入data并執(zhí)行observer:
    image.png

    接著對(duì)所有data中的屬性綁定gettersetter,這一步需要進(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í)行即可

  • 更新視圖

  1. 更新視圖的時(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
  2. Observersetter中觸發(fā)Depnotify方法,進(jìn)行視圖的更新:
    image.png
  3. 到了這步其實(shí)就已經(jīng)達(dá)成效果了:


    image.png
  • 修復(fù)bug
    雖然MVVM雙向綁定的功能已經(jīng)達(dá)成,但是還是有不少bug的,其中最嚴(yán)重的有兩個(gè)
  1. 當(dāng)我們多次更新數(shù)據(jù)的時(shí)候,會(huì)發(fā)現(xiàn)添加進(jìn)subswatcher發(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

  2. 當(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以及computedwatch的支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容