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

再次看了上次寫的博客關于Vue的MVVM,發現雖然介紹了MVVM的原理,但是感覺還不夠詳細,現在就再次根據這篇博客寫詳細一點,來看看new Vue的時候Vue究竟做了些什么事。
我想,以需求作為出發點來理解原理會比較容易,所以這篇博客會以提出需求 -> 解決需求的方式來寫。

項目地址,歡迎start

Vue中的MVVM原理介紹

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

image.png

回顧

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

image.png

image.png

但還未完成view -> model的綁定,所以不能通過修改數據來觸發視圖的更新,今天就來完成剩余部分

提出需求

繼上圖:


image.png

我們需要做到的是:當修改輸入框的數據時,上面的文字也隨之進行刷新。

分析

先來看看還未完成的部分:

image.png

其中包含觀察者Observer,監聽器Watcher,然后還有一個Dep,過程是:

  1. Compiler中監聽數據的變化并綁定監聽器;
  2. 在觀察者Observer中實現對所有數據的gettersetter
  3. 監聽器Watcher把更新事件添加進Dep的事件隊列中;
  4. 觀察者Observer發現數據產生變化的時候通知Dep
  5. Dep把事件隊列中的更新事件全部執行一遍;

總結下來就是實現兩個事情:

  1. 添加數據依賴;
  2. 觸發數據變更事件;
    接下來就先創建WatcherObserverDep三個類;
    image.png

    image.png

    image.png

先來看看Dep是什么

根據上面的的步驟描述,很容易感覺到Dep像是一個容器,存儲著對視圖的更新事件,是的,這是一個發布訂閱模式的實現,該模式包含事件隊列subs,添加事件方法addSub,執行事件隊列函數notify,移除事件隊列里的事件removeSub

image.png

看完發布訂閱模式后,繼續我們的流程。

添加依賴

  • 在Compiler中監聽數據的變化并綁定監聽器
    在上一篇對model -> view的綁定中我們有一個針對數據和指令統一進行綁定的方法bind,為了不和后面的v-bind指令沖突,現在改為了bindData;

    image.png

    在這個函數承載的功能有獲取tag文本和執行視圖的更新,所以我們可以在這個函數中添加對數據的監聽器Watcher,因為后面需要把更新視圖的事件添加進Dep中,所以Watcher中需要的參數要有一個更新事件也就是更新器updater中的視圖更新函數,此外將當前vm實例和得到的data鍵值也傳進去備用;
    compile中添加數據監聽器

    更新器

    watcher

    接著

  • 在觀察者Observer中實現對所有數據的getter和setter
    注意這一步需要考慮到數據中含有嵌套的對象,需要進行遞歸操作才能全部添加gettersetter,使用的是Object.definedPropertyObserver接受的參數是data對象:

    image.png

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

    接著對所有data中的屬性綁定gettersetter,這一步需要進行遞歸操作:
    image.png

    最后回到Compiler中,實現視圖對數據的修改:
    比如在輸入框中修改數據直接反應到data
    image.png

    image.png

    來看看成果:
    image.png

    這個時候在視圖上對數據進行的修改就可以反映到data上,并觸發該數據的setter函數;

  • 監聽器Watcher把更新事件添加進Dep的事件隊列中;
    這一步需要考慮一個問題:在什么時候怎么樣把更新事件添加到Dep中去?
    回顧上面所寫的,data中的每一個屬性都有一個對應的Watcher,可以在Watcher中獲取得到對應的data中的屬性。那么在這個獲取的過程中,又會觸發該屬性的getter,就可以考慮在該屬性的getter中添加,分解成一下步驟就是:
    ① 把這個Watcher通過構造函數本身的屬性target保留在Dep中,然后去data中取值;

    image.png

    ② 取值的時候觸發Observer中該屬性的getter,在Observer中new一個Dep實例出來,判斷如果Dep類的target非空(也就是該屬性已被有監聽器),則觸發依賴添加事件depend;
    image.png

    ③ 這時候的Dep.target就是被監聽屬性的Watcher,在Dep類中添加一個方法depend,用來把該屬性的Watcher添加進事件隊列subs中,但是這一步要當前的Watcher,需要在Watcher類中進行觸發,所以在Watcher中創建一個函數addDep,把Dep的實例作為參數放進去,然后在addDep中進行更新事件的添加:
    image.png

    image.png

    然后置空Dep.target,用于下一個數據的依賴添加
    image.png

    現在我們來看看subs中有些什么
    image.png

    可見msg被引用了兩次就被監聽了兩次,這時候只要當msg這個數據發生變化并觸發setter時,將subs中所有的watcher實例里的更新回調update拉出來執行即可

  • 更新視圖

  1. 更新視圖的時候,我們先要獲取當前的數據新值,然后作為參數放進回調函數中,并且還要對新的數據進行上面的依賴添加步驟,那么Watcher還需要一個update函數用來統一做這個事:
    image.png
  2. Observersetter中觸發Depnotify方法,進行視圖的更新:
    image.png
  3. 到了這步其實就已經達成效果了:


    image.png
  • 修復bug
    雖然MVVM雙向綁定的功能已經達成,但是還是有不少bug的,其中最嚴重的有兩個
  1. 當我們多次更新數據的時候,會發現添加進subswatcher發生了遞增的現象,所以當快速更新數據時就會導致執行函數過多而頁面崩潰;

    image.png

    造成這個現象的原因是在進行第一次的更新時,watcher將同一個數據的新值也進行了依賴添加,也就是let newVal = this.get()這一段;
    image.png

    既然知道了原因,那么解決起來也很簡單,給每一個被監聽的對象都添加一個id即可。
    因為添加sub的操作是在Watcher中進行的,所以在Watcher中創建一個對象depIds
    image.png

    然后給每一個Dep都添加一個不同的id
    image.png

    最后在Watcher中判斷depIds是否已經有這個id的Dep實例存在,如果沒有則添加進去并執行addSub,否則不執行:
    image.png

    效果,無論怎么修改,都只會有固定數量的Watcher存在:
    image.png

  2. 當修改數據為對象的時候,這個對象沒有進行監聽,這個也好解決,只要在setter中進行判斷即可,若為對象則針對該對象重新進行監聽

    image.png

總結

到這里為止,我們就完成了view -< model的綁定,并且知道在new Vue的時候大致做了一些什么事了,剩下的就是逐步完善,例如對更多指令的支持,對methods以及computedwatch的支持。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容