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

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

項目地址,歡迎start

Vue中的MVVM原理介紹

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

image.png

需求提出

首先我們來看Vue最最基礎的用法--在id為app的元素上顯示出數據msg的內容,平時都是Vue(Vue技術棧的話)在做這件事,那么給我們自己如何實現呢?

  • 需求


    image.png
  • 達成效果


    image.png
  • 總結:
    這一步是model(數據模型) -> view(視圖)的綁定

需求分析

  • ①:首先我們要根據el獲取得到當前元素;
  • ②:過濾出其中符合mustache語法{{...}}中的字符;
  • ③:根據②拿到的字符取到data對象中相應的數據并對對應的整個{{..}}字符進行替換;

創建MVVM類

  • 創建MVVM類進行初始化
    我們給自己寫的Vue命名為MVVM,創建一個MVVM的類,然后獲取其中的eldata

    image.png

    image.png

  • 但是這時候會發現,如果需要拿到data中的數據需要通過this.data.msg才能拿到,而平時用Vue時在實例中只需要this.msg就能拿到,所以就出現一個問題:如何才能msg放到當前的vm實例中呢?

    image.png

  • 將data中的屬性代理到當前vm實例中
    答案是用代理的方式,這里就涉及到一個屬性Object.defineProperty(該屬性是Vue的核心,不了解的可要看一下哦),代碼如下:

    image.png

    image.png

  • 總結:這一步就是new MVVM時做的一部分初始化工作,下一步是獲取el的節點,并進行編譯工作

節點編譯器(Compiler)

  • 創建Compiler類
    這個類的職能是獲取data中的數據,并對節點中的{{...}}進行替換,所以需要傳入的參數有el和當前的vm實例;

    image.png

    image.png

  • 創建節點副本
    如果直接操控DOM元素,所需要的性能花銷較大,所以在Vue中采用了假節點(createDocumentFragment),通過更新假節點然后替換當前節點的方式。
    注意:在這一塊中,創建出來的假節點fragment對應的是el節點,所以方法是將el節點內的所有子節點都扔進fragment中,如下代碼:

    image.png

  • 對節點副本進行編譯

    • 因為fragment跟隨的是el節點,所以需要考慮一個情況:fragment的子節點中有文本節點和元素節點兩種,這時候就需要分情況進行編譯了,這里可以通過[node.nodeType]

      image.png

    • 文本節點編譯
      文字節點的編譯又需要考慮多種情況:
      ①:{{...}}前面是否有普通文本msg{{msg}};
      ②:{{...}}后面是否有普通文本{{msg}}msg;
      ③:{{..}}里面有可能是某個對象中的值{{message.msg}}
      這時候我們把情況修改得復雜一些,包含上面所有的情況:

      image.png

      • 設想:
        如何解決問題①和問題②:創建一個數組,然后將截取第一個{{...}}文本之前的普通文本,作為普通文本放進數組中,然后再截取一個{{...}}文本作為tag文本放進數組中,以此類推對整個文本進行分割;
        所以我們需要做的有1.創建一個數組用于容納分割后的文本textLIst;2.創建一個能過濾出{{...}}文本的正則;3.分割出{{...}}文本中的鍵:比如{{msg}} 分割出 msg;5.創建用于定位{{...}}左后一個花括號所在index的坐標lastIndex,初始值為0;6. 事先預備一個match作為備用的正則匹配項;如下:

        image.png

      • 解決問題①:
        考慮到文本中可能有多個{{...}}文本的存在,并且還需要知道{{...}}所處的index,所以使用RegExp.prototype.exec就可以直接得到{{...}}里的鍵名,index的值,然后賦值給match,之后套入到一個while循環中執行,千萬不能寫成這樣,會導致死循環,原因在上面exec的mdn文檔中有解釋:

        image.png

        需要這樣寫:
        image.png

        之后有幾個{{...}}文本,while循環就會執行幾次,得出match的值:
        image.png

        如上圖,因為已經擁有了index,所以我們現在可以將{{...}}前的文字拿出來放進textList中:
        image.png

        然后將{{...}}里的鍵名作為tag傳入textList中并將lastIndex置為該{{...}}文本之后:
        image.png

        這時候打印textList發現從最后一個{{...}}文本到文本開頭的的所有文本都已經做好了分類,并且lastIndex也已經為最后一個{{...}}的最后一個花括號所在的index了:
        image.png

        剩下的就是將剩余文本也作為普通文本放入textList中,因為上面的lastIndex的位置,所以我們直接通過lastIndex和文本的長度判斷可知最后面是否有文本需要進行compile:
        image.png

        image.png

        注意這一步不能放在while循環中做,否則會導致重復的文本放入。
        image.png

      • 我們將上面的步驟抽離出來單獨作為一個函數compileText,并將textList返回出去;

        image.png

        到了這一步實際上我們已經拿到了文本的分類片段,是{{...}}的文本則為tag,否則為普通文本,下一步就是進行文本的替換了

      • 進行文本替換
        這一步中我們的主要工作是對textNode文本節點中的文本進行替換,那么需要做的步驟如下:
        1 .拿到文本節點的父節點并創建一個用于替換的假節點;

        image.png

        2 .遍歷textList中進行過分類的文本片段,然后進行判斷,如果非tag文本則據此創建一個文本節點并放進假節點中。
        image.png

        3 .如果是tag文本則在data中進行取值,但是這時候要考慮一個問題了,文本中的{{XXX}}實際上是一個v-text="XXX"指令,Vue中還有很多指令,例如v-model,v-for等,他們都需要在data中進行取值綁定等操作,這樣的話就需要一個專門用于依據類型進行取值,綁定等工作的指令集合directives,并且當前directives里面需要一個專門處理v-text的方法、一個專門用于綁定的bind方法以及一個專門用于取值的getVMData方法,然后還需要一個專門根據指令類型用于綁定后更新視圖的集合updater,里面同樣需要一個text方法:
        image.png

        4 .之后我們在當文本類型為tag時創建一個空的文本節點el, 然后思考text指令所承載的功能,并傳入el、當前vm實例、tag文本的值,并標明類型為text:
        image.png

        image.png

        5 .解決問題③,在對tag進行綁定的過程中,免不了要先去獲取到tag文本在data中對應的值,這時候就需要考慮問題③中{{message.name}}這樣的情況了,可以使用字符串方法split基于.分割成的數組獲取到在data中正確的值:
        image.png

  1. 現在我們已經拿到了data中對應tag的值newVal,并且也有了相對應的節點,那么就執行更新器updater中對應類型的更新方法就可以了,在這里,就是更新相對應節點的文本內容textContent
    image.png
  2. 最后回到compileTextNode函數中,將compile好的文本節點放進假節點中,再將textNode父節點中的文本替換即可
    image.png
  3. 這時候compile文本節點的工作就已經做完了,將處理后的fragment插入到真實節點el中就可以看到效果了:
    image.png

    image.png
  4. 但是此時還沒有針對普通節點進行compile,所以如下html無法正常顯示,下一步就是對節點進行compile:


    image.png

    image.png

對節點進行compile(進入compileNodeElement函數)

由上圖的html可以知道,針對節點進行的compile需要分為兩類:

  1. 普通節點的compile,也就是節點內只有文本,例如<div>msg{{msg}}msg{{message.name}}8888888</div>;
  2. 帶有指令的的節點,例如:<input type="text" v-model="msg">
  • 對普通節點compile
    對普通節點進行compile很簡單,因為已經有了針對文本節點的compile,那么只需要創建一個通道,讓普通節點進入文本節點的compile即可:


    image.png

    結果:


    image.png
  • 帶有指令的節點的compile(這里只針對v-model指令進行)
    對帶有指令的節點的節點進行compile需要做如下幾件事:

      1. 獲取節點的所有屬性名字,然后遍歷,判斷是否存在指令:


        image.png
      1. 如果存在指令,就獲取該指令的值和指令類型,例如v-model=msg就獲取modelmsg
        image.png
      1. 在指令集directive和更新器updater中添加相應方法,這里是model方法,并進行處理
        image.png

        image.png
      1. 結果:


        image.png

        image.png

總結

該篇博客只對Vue中的初始化渲染原理做了介紹,也只是完成了流程圖中Compile的大部分model -> view的綁定,但還未達成雙向綁定,因此對數據的修改并不能對視圖進行改變,這就是下一篇博客view -> model的綁定所要介紹的:

image.png

Vue中的MVVM--view -> model的綁定

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

推薦閱讀更多精彩內容