再次看了上次寫的博客關于Vue的MVVM,發現雖然介紹了MVVM的原理,但是感覺還不夠詳細,現在就再次根據這篇博客寫詳細一點,來看看new Vue
的時候Vue究竟做了些什么事。
我想,以需求作為出發點來理解原理會比較容易,所以這篇博客會以提出需求 -> 解決需求的方式來寫。
Vue中的MVVM原理介紹
可以先閱讀我的這篇博客了解一下關于Vue的MVVM,另外需要記住這一幅圖(很重要),這張圖就是本篇博客的概括:
需求提出
首先我們來看Vue最最基礎的用法--在id為app的元素上顯示出數據msg的內容,平時都是Vue(Vue技術棧的話)在做這件事,那么給我們自己如何實現呢?
-
需求
image.png -
達成效果
image.png - 總結:
這一步是model(數據模型) -> view(視圖)的綁定
需求分析
- ①:首先我們要根據el獲取得到當前元素;
- ②:過濾出其中符合mustache語法
{{...}}
中的字符; - ③:根據②拿到的字符取到data對象中相應的數據并對對應的整個
{{..}}
字符進行替換;
創建MVVM類
-
創建MVVM類進行初始化
我們給自己寫的Vue命名為MVVM,創建一個MVVM的類,然后獲取其中的el
和data
;
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
-
-
- 現在我們已經拿到了
data
中對應tag的值newVal,并且也有了相對應的節點,那么就執行更新器updater
中對應類型的更新方法就可以了,在這里,就是更新相對應節點的文本內容textContent
,
image.png - 最后回到
compileTextNode
函數中,將compile好的文本節點放進假節點中,再將textNode
父節點中的文本替換即可
image.png - 這時候compile文本節點的工作就已經做完了,將處理后的
fragment
插入到真實節點el
中就可以看到效果了:
image.png
image.png -
但是此時還沒有針對普通節點進行compile,所以如下html無法正常顯示,下一步就是對節點進行compile:
image.png
image.png
對節點進行compile(進入compileNodeElement
函數)
由上圖的html可以知道,針對節點進行的compile需要分為兩類:
- 普通節點的compile,也就是節點內只有文本,例如
<div>msg{{msg}}msg{{message.name}}8888888</div>
; - 帶有指令的的節點,例如:
<input type="text" v-model="msg">
-
對普通節點compile
對普通節點進行compile很簡單,因為已經有了針對文本節點的compile,那么只需要創建一個通道,讓普通節點進入文本節點的compile即可:
image.png
結果:
image.png -
帶有指令的節點的compile(這里只針對
v-model
指令進行)
對帶有指令的節點的節點進行compile需要做如下幾件事:-
獲取節點的所有屬性名字,然后遍歷,判斷是否存在指令:
image.png
-
- 如果存在指令,就獲取該指令的值和指令類型,例如
v-model=msg
就獲取model
和msg
image.png
- 如果存在指令,就獲取該指令的值和指令類型,例如
- 在指令集
directive
和更新器updater
中添加相應方法,這里是model
方法,并進行處理
image.png
image.png
- 在指令集
-
結果:
image.png
image.png
-
總結
該篇博客只對Vue中的初始化渲染原理做了介紹,也只是完成了流程圖中Compile
的大部分model -> view
的綁定,但還未達成雙向綁定,因此對數據的修改并不能對視圖進行改變,這就是下一篇博客view -> model
的綁定所要介紹的: