再次看了上次寫的博客關于Vue的MVVM卵迂,發(fā)現(xiàn)雖然介紹了MVVM的原理裕便,但是感覺還不夠詳細,現(xiàn)在就再次根據(jù)這篇博客寫詳細一點狭握,來看看new Vue
的時候Vue究竟做了些什么事闪金。
我想,以需求作為出發(fā)點來理解原理會比較容易论颅,所以這篇博客會以提出需求 -> 解決需求的方式來寫哎垦。
Vue中的MVVM原理介紹
可以先閱讀我的這篇博客了解一下關于Vue的MVVM恃疯,另外需要記住這一幅圖(很重要)漏设,這張圖就是本篇博客的概括:
需求提出
首先我們來看Vue最最基礎的用法--在id為app的元素上顯示出數(shù)據(jù)msg的內(nèi)容,平時都是Vue(Vue技術棧的話)在做這件事今妄,那么給我們自己如何實現(xiàn)呢郑口?
-
需求
image.png -
達成效果
image.png - 總結(jié):
這一步是model(數(shù)據(jù)模型) -> view(視圖)的綁定
需求分析
- ①:首先我們要根據(jù)el獲取得到當前元素鸳碧;
- ②:過濾出其中符合mustache語法
{{...}}
中的字符; - ③:根據(jù)②拿到的字符取到data對象中相應的數(shù)據(jù)并對對應的整個
{{..}}
字符進行替換犬性;
創(chuàng)建MVVM類
-
創(chuàng)建MVVM類進行初始化
我們給自己寫的Vue命名為MVVM瞻离,創(chuàng)建一個MVVM的類,然后獲取其中的el
和data
乒裆;
image.png
image.png -
但是這時候會發(fā)現(xiàn)套利,如果需要拿到
data
中的數(shù)據(jù)需要通過this.data.msg
才能拿到,而平時用Vue時在實例中只需要this.msg
就能拿到鹤耍,所以就出現(xiàn)一個問題:如何才能msg
放到當前的vm實例中呢肉迫?
image.png -
將data中的屬性代理到當前vm實例中
答案是用代理的方式,這里就涉及到一個屬性Object.defineProperty(該屬性是Vue的核心稿黄,不了解的可要看一下哦)喊衫,代碼如下:
image.png
image.png 總結(jié):這一步就是
new MVVM
時做的一部分初始化工作,下一步是獲取el
的節(jié)點杆怕,并進行編譯工作
節(jié)點編譯器(Compiler)
-
創(chuàng)建Compiler類
這個類的職能是獲取data
中的數(shù)據(jù)族购,并對節(jié)點中的{{...}}
進行替換,所以需要傳入的參數(shù)有el
和當前的vm實例财著;
image.png
image.png -
創(chuàng)建節(jié)點副本
如果直接操控DOM元素联四,所需要的性能花銷較大撑碴,所以在Vue中采用了假節(jié)點(createDocumentFragment)撑教,通過更新假節(jié)點然后替換當前節(jié)點的方式。
注意:在這一塊中醉拓,創(chuàng)建出來的假節(jié)點fragment
對應的是el
節(jié)點伟姐,所以方法是將el
節(jié)點內(nèi)的所有子節(jié)點都扔進fragment中
,如下代碼:
image.png -
對節(jié)點副本進行編譯
-
因為
fragment
跟隨的是el
節(jié)點亿卤,所以需要考慮一個情況:fragment
的子節(jié)點中有文本節(jié)點和元素節(jié)點兩種愤兵,這時候就需要分情況進行編譯了,這里可以通過[node.nodeType]image.png -
文本節(jié)點編譯
文字節(jié)點的編譯又需要考慮多種情況:
①:{{...}}
前面是否有普通文本msg{{msg}}
;
②:{{...}}
后面是否有普通文本{{msg}}msg
;
③:{{..}}
里面有可能是某個對象中的值{{message.msg}}
排吴;
這時候我們把情況修改得復雜一些秆乳,包含上面所有的情況:
image.png-
設想:
如何解決問題①和問題②:創(chuàng)建一個數(shù)組,然后將截取第一個{{...}}
文本之前的普通文本钻哩,作為普通文本放進數(shù)組中屹堰,然后再截取一個{{...}}
文本作為tag文本放進數(shù)組中,以此類推對整個文本進行分割街氢;
所以我們需要做的有1.創(chuàng)建一個數(shù)組用于容納分割后的文本textLIst
扯键;2.創(chuàng)建一個能過濾出{{...}}
文本的正則;3.分割出{{...}}
文本中的鍵:比如{{msg}} 分割出 msg
珊肃;5.創(chuàng)建用于定位{{...}}
左后一個花括號所在index的坐標lastIndex荣刑,初始值為0馅笙;6. 事先預備一個match作為備用的正則匹配項;如下:
image.png -
解決問題①:
考慮到文本中可能有多個{{...}}
文本的存在厉亏,并且還需要知道{{...}}
所處的index董习,所以使用RegExp.prototype.exec就可以直接得到{{...}}
里的鍵名,index的值爱只,然后賦值給match阱飘,之后套入到一個while
循環(huán)中執(zhí)行,千萬不能寫成這樣,會導致死循環(huán)虱颗,原因在上面exec
的mdn文檔中有解釋:
image.png
需要這樣寫:
image.png
之后有幾個{{...}}
文本沥匈,while
循環(huán)就會執(zhí)行幾次,得出match的值:
image.png
如上圖忘渔,因為已經(jīng)擁有了index高帖,所以我們現(xiàn)在可以將{{...}}
前的文字拿出來放進textList
中:
image.png
然后將{{...}}里的鍵名作為tag傳入textList
中并將lastIndex
置為該{{...}}
文本之后:
image.png
這時候打印textList
發(fā)現(xiàn)從最后一個{{...}}
文本到文本開頭的的所有文本都已經(jīng)做好了分類,并且lastIndex
也已經(jīng)為最后一個{{...}}
的最后一個花括號所在的index了:
image.png
剩下的就是將剩余文本也作為普通文本放入textList
中畦粮,因為上面的lastIndex的位置散址,所以我們直接通過lastIndex
和文本的長度判斷可知最后面是否有文本需要進行compile:
image.png
image.png
注意這一步不能放在while循環(huán)中做,否則會導致重復的文本放入宣赔。
image.png -
我們將上面的步驟抽離出來單獨作為一個函數(shù)
compileText
预麸,并將textList
返回出去;
image.png
到了這一步實際上我們已經(jīng)拿到了文本的分類片段,是{{...}}
的文本則為tag儒将,否則為普通文本吏祸,下一步就是進行文本的替換了 -
進行文本替換
這一步中我們的主要工作是對textNode
文本節(jié)點中的文本進行替換,那么需要做的步驟如下:
1 .拿到文本節(jié)點的父節(jié)點并創(chuàng)建一個用于替換的假節(jié)點钩蚊;
image.png
2 .遍歷textList
中進行過分類的文本片段贡翘,然后進行判斷,如果非tag文本則據(jù)此創(chuàng)建一個文本節(jié)點并放進假節(jié)點中砰逻。
image.png
3 .如果是tag文本則在data
中進行取值鸣驱,但是這時候要考慮一個問題了,文本中的{{XXX}}
實際上是一個v-text="XXX"
指令蝠咆,Vue中還有很多指令踊东,例如v-model
,v-for
等,他們都需要在data
中進行取值綁定等操作刚操,這樣的話就需要一個專門用于依據(jù)類型進行取值闸翅,綁定等工作的指令集合directives
,并且當前directives
里面需要一個專門處理v-text
的方法赡茸、一個專門用于綁定的bind
方法以及一個專門用于取值的getVMData
方法缎脾,然后還需要一個專門根據(jù)指令類型用于綁定后更新視圖的集合updater
,里面同樣需要一個text
方法:
image.png
4 .之后我們在當文本類型為tag時創(chuàng)建一個空的文本節(jié)點el
占卧, 然后思考text
指令所承載的功能遗菠,并傳入el
联喘、當前vm實例、tag文本的值辙纬,并標明類型為text:
image.png
image.png
5 .解決問題③豁遭,在對tag進行綁定的過程中,免不了要先去獲取到tag文本在data
中對應的值贺拣,這時候就需要考慮問題③中{{message.name}}
這樣的情況了蓖谢,可以使用字符串方法split
基于.
分割成的數(shù)組獲取到在data
中正確的值:
image.png
-
-
- 現(xiàn)在我們已經(jīng)拿到了
data
中對應tag的值newVal,并且也有了相對應的節(jié)點譬涡,那么就執(zhí)行更新器updater
中對應類型的更新方法就可以了闪幽,在這里,就是更新相對應節(jié)點的文本內(nèi)容textContent
涡匀,
image.png - 最后回到
compileTextNode
函數(shù)中盯腌,將compile好的文本節(jié)點放進假節(jié)點中,再將textNode
父節(jié)點中的文本替換即可
image.png - 這時候compile文本節(jié)點的工作就已經(jīng)做完了陨瘩,將處理后的
fragment
插入到真實節(jié)點el
中就可以看到效果了:
image.png
image.png -
但是此時還沒有針對普通節(jié)點進行compile腕够,所以如下html無法正常顯示,下一步就是對節(jié)點進行compile:
image.png
image.png
對節(jié)點進行compile(進入compileNodeElement
函數(shù))
由上圖的html可以知道舌劳,針對節(jié)點進行的compile需要分為兩類:
- 普通節(jié)點的compile帚湘,也就是節(jié)點內(nèi)只有文本,例如
<div>msg{{msg}}msg{{message.name}}8888888</div>
; - 帶有指令的的節(jié)點甚淡,例如:
<input type="text" v-model="msg">
-
對普通節(jié)點compile
對普通節(jié)點進行compile很簡單大诸,因為已經(jīng)有了針對文本節(jié)點的compile,那么只需要創(chuàng)建一個通道材诽,讓普通節(jié)點進入文本節(jié)點的compile即可:
image.png
結(jié)果:
image.png -
帶有指令的節(jié)點的compile(這里只針對
v-model
指令進行)
對帶有指令的節(jié)點的節(jié)點進行compile需要做如下幾件事:-
獲取節(jié)點的所有屬性名字底挫,然后遍歷,判斷是否存在指令:
image.png
-
- 如果存在指令脸侥,就獲取該指令的值和指令類型,例如
v-model=msg
就獲取model
和msg
image.png
- 如果存在指令脸侥,就獲取該指令的值和指令類型,例如
- 在指令集
directive
和更新器updater
中添加相應方法盈厘,這里是model
方法睁枕,并進行處理
image.png
image.png
- 在指令集
-
結(jié)果:
image.png
image.png
-
總結(jié)
該篇博客只對Vue中的初始化渲染原理做了介紹,也只是完成了流程圖中Compile
的大部分model -> view
的綁定沸手,但還未達成雙向綁定外遇,因此對數(shù)據(jù)的修改并不能對視圖進行改變,這就是下一篇博客view -> model
的綁定所要介紹的: