再次看了上次寫的博客關(guān)于Vue的MVVM,發(fā)現(xiàn)雖然介紹了MVVM的原理,但是感覺還不夠詳細(xì),現(xiàn)在就再次根據(jù)這篇博客寫詳細(xì)一點(diǎn),來看看new Vue的時候Vue究竟做了些什么事。
我想,以需求作為出發(fā)點(diǎn)來理解原理會比較容易,所以這篇博客會以提出需求 -> 解決需求的方式來寫。
Vue中的MVVM原理介紹
可以先閱讀我的這篇博客了解一下關(guān)于Vue的MVVM,另外需要記住這一幅圖(很重要),這張圖就是本篇博客的概括:

需求提出
首先我們來看Vue最最基礎(chǔ)的用法--在id為app的元素上顯示出數(shù)據(jù)msg的內(nèi)容,平時都是Vue(Vue技術(shù)棧的話)在做這件事,那么給我們自己如何實(shí)現(xiàn)呢?
-
需求
image.png -
達(dá)成效果
image.png - 總結(jié):
這一步是model(數(shù)據(jù)模型) -> view(視圖)的綁定
需求分析
- ①:首先我們要根據(jù)el獲取得到當(dāng)前元素;
- ②:過濾出其中符合mustache語法
{{...}}中的字符; - ③:根據(jù)②拿到的字符取到data對象中相應(yīng)的數(shù)據(jù)并對對應(yīng)的整個
{{..}}字符進(jìn)行替換;
創(chuàng)建MVVM類
-
創(chuàng)建MVVM類進(jìn)行初始化
我們給自己寫的Vue命名為MVVM,創(chuàng)建一個MVVM的類,然后獲取其中的el和data;
image.png
image.png -
但是這時候會發(fā)現(xiàn),如果需要拿到
data中的數(shù)據(jù)需要通過this.data.msg才能拿到,而平時用Vue時在實(shí)例中只需要this.msg就能拿到,所以就出現(xiàn)一個問題:如何才能msg放到當(dāng)前的vm實(shí)例中呢?
image.png -
將data中的屬性代理到當(dāng)前vm實(shí)例中
答案是用代理的方式,這里就涉及到一個屬性Object.defineProperty(該屬性是Vue的核心,不了解的可要看一下哦),代碼如下:
image.png
image.png 總結(jié):這一步就是
new MVVM時做的一部分初始化工作,下一步是獲取el的節(jié)點(diǎn),并進(jìn)行編譯工作
節(jié)點(diǎn)編譯器(Compiler)
-
創(chuàng)建Compiler類
這個類的職能是獲取data中的數(shù)據(jù),并對節(jié)點(diǎn)中的{{...}}進(jìn)行替換,所以需要傳入的參數(shù)有el和當(dāng)前的vm實(shí)例;
image.png
image.png -
創(chuàng)建節(jié)點(diǎn)副本
如果直接操控DOM元素,所需要的性能花銷較大,所以在Vue中采用了假節(jié)點(diǎn)(createDocumentFragment),通過更新假節(jié)點(diǎn)然后替換當(dāng)前節(jié)點(diǎn)的方式。
注意:在這一塊中,創(chuàng)建出來的假節(jié)點(diǎn)fragment對應(yīng)的是el節(jié)點(diǎn),所以方法是將el節(jié)點(diǎn)內(nèi)的所有子節(jié)點(diǎn)都扔進(jìn)fragment中,如下代碼:
image.png -
對節(jié)點(diǎn)副本進(jìn)行編譯
-
因?yàn)?code>fragment跟隨的是
el節(jié)點(diǎn),所以需要考慮一個情況:fragment的子節(jié)點(diǎn)中有文本節(jié)點(diǎn)和元素節(jié)點(diǎn)兩種,這時候就需要分情況進(jìn)行編譯了,這里可以通過[node.nodeType]image.png -
文本節(jié)點(diǎn)編譯
文字節(jié)點(diǎn)的編譯又需要考慮多種情況:
①:{{...}}前面是否有普通文本msg{{msg}};
②:{{...}}后面是否有普通文本{{msg}}msg;
③:{{..}}里面有可能是某個對象中的值{{message.msg}};
這時候我們把情況修改得復(fù)雜一些,包含上面所有的情況:
image.png-
設(shè)想:
如何解決問題①和問題②:創(chuàng)建一個數(shù)組,然后將截取第一個{{...}}文本之前的普通文本,作為普通文本放進(jìn)數(shù)組中,然后再截取一個{{...}}文本作為tag文本放進(jìn)數(shù)組中,以此類推對整個文本進(jìn)行分割;
所以我們需要做的有1.創(chuàng)建一個數(shù)組用于容納分割后的文本textLIst;2.創(chuàng)建一個能過濾出{{...}}文本的正則;3.分割出{{...}}文本中的鍵:比如{{msg}} 分割出 msg;5.創(chuàng)建用于定位{{...}}左后一個花括號所在index的坐標(biāo)lastIndex,初始值為0;6. 事先預(yù)備一個match作為備用的正則匹配項(xiàng);如下:
image.png -
解決問題①:
考慮到文本中可能有多個{{...}}文本的存在,并且還需要知道{{...}}所處的index,所以使用RegExp.prototype.exec就可以直接得到{{...}}里的鍵名,index的值,然后賦值給match,之后套入到一個while循環(huán)中執(zhí)行,千萬不能寫成這樣,會導(dǎo)致死循環(huán),原因在上面exec的mdn文檔中有解釋:
image.png
需要這樣寫:
image.png
之后有幾個{{...}}文本,while循環(huán)就會執(zhí)行幾次,得出match的值:
image.png
如上圖,因?yàn)橐呀?jīng)擁有了index,所以我們現(xiàn)在可以將{{...}}前的文字拿出來放進(jìn)textList中:
image.png
然后將{{...}}里的鍵名作為tag傳入textList中并將lastIndex置為該{{...}}文本之后:
image.png
這時候打印textList發(fā)現(xiàn)從最后一個{{...}}文本到文本開頭的的所有文本都已經(jīng)做好了分類,并且lastIndex也已經(jīng)為最后一個{{...}}的最后一個花括號所在的index了:
image.png
剩下的就是將剩余文本也作為普通文本放入textList中,因?yàn)樯厦娴膌astIndex的位置,所以我們直接通過lastIndex和文本的長度判斷可知最后面是否有文本需要進(jìn)行compile:
image.png
image.png
注意這一步不能放在while循環(huán)中做,否則會導(dǎo)致重復(fù)的文本放入。
image.png -
我們將上面的步驟抽離出來單獨(dú)作為一個函數(shù)
compileText,并將textList返回出去;
image.png
到了這一步實(shí)際上我們已經(jīng)拿到了文本的分類片段,是{{...}}的文本則為tag,否則為普通文本,下一步就是進(jìn)行文本的替換了 -
進(jìn)行文本替換
這一步中我們的主要工作是對textNode文本節(jié)點(diǎn)中的文本進(jìn)行替換,那么需要做的步驟如下:
1 .拿到文本節(jié)點(diǎn)的父節(jié)點(diǎn)并創(chuàng)建一個用于替換的假節(jié)點(diǎn);
image.png
2 .遍歷textList中進(jìn)行過分類的文本片段,然后進(jìn)行判斷,如果非tag文本則據(jù)此創(chuàng)建一個文本節(jié)點(diǎn)并放進(jìn)假節(jié)點(diǎn)中。
image.png
3 .如果是tag文本則在data中進(jìn)行取值,但是這時候要考慮一個問題了,文本中的{{XXX}}實(shí)際上是一個v-text="XXX"指令,Vue中還有很多指令,例如v-model,v-for等,他們都需要在data中進(jìn)行取值綁定等操作,這樣的話就需要一個專門用于依據(jù)類型進(jìn)行取值,綁定等工作的指令集合directives,并且當(dāng)前directives里面需要一個專門處理v-text的方法、一個專門用于綁定的bind方法以及一個專門用于取值的getVMData方法,然后還需要一個專門根據(jù)指令類型用于綁定后更新視圖的集合updater,里面同樣需要一個text方法:
image.png
4 .之后我們在當(dāng)文本類型為tag時創(chuàng)建一個空的文本節(jié)點(diǎn)el, 然后思考text指令所承載的功能,并傳入el、當(dāng)前vm實(shí)例、tag文本的值,并標(biāo)明類型為text:
image.png
image.png
5 .解決問題③,在對tag進(jìn)行綁定的過程中,免不了要先去獲取到tag文本在data中對應(yīng)的值,這時候就需要考慮問題③中{{message.name}}這樣的情況了,可以使用字符串方法split基于.分割成的數(shù)組獲取到在data中正確的值:
image.png
-
-
- 現(xiàn)在我們已經(jīng)拿到了
data中對應(yīng)tag的值newVal,并且也有了相對應(yīng)的節(jié)點(diǎn),那么就執(zhí)行更新器updater中對應(yīng)類型的更新方法就可以了,在這里,就是更新相對應(yīng)節(jié)點(diǎn)的文本內(nèi)容textContent,
image.png - 最后回到
compileTextNode函數(shù)中,將compile好的文本節(jié)點(diǎn)放進(jìn)假節(jié)點(diǎn)中,再將textNode父節(jié)點(diǎn)中的文本替換即可
image.png - 這時候compile文本節(jié)點(diǎn)的工作就已經(jīng)做完了,將處理后的
fragment插入到真實(shí)節(jié)點(diǎn)el中就可以看到效果了:
image.png
image.png -
但是此時還沒有針對普通節(jié)點(diǎn)進(jìn)行compile,所以如下html無法正常顯示,下一步就是對節(jié)點(diǎn)進(jìn)行compile:
image.png
image.png
對節(jié)點(diǎn)進(jìn)行compile(進(jìn)入compileNodeElement函數(shù))
由上圖的html可以知道,針對節(jié)點(diǎn)進(jìn)行的compile需要分為兩類:
- 普通節(jié)點(diǎn)的compile,也就是節(jié)點(diǎn)內(nèi)只有文本,例如
<div>msg{{msg}}msg{{message.name}}8888888</div>; - 帶有指令的的節(jié)點(diǎn),例如:
<input type="text" v-model="msg">
-
對普通節(jié)點(diǎn)compile
對普通節(jié)點(diǎn)進(jìn)行compile很簡單,因?yàn)橐呀?jīng)有了針對文本節(jié)點(diǎn)的compile,那么只需要創(chuàng)建一個通道,讓普通節(jié)點(diǎn)進(jìn)入文本節(jié)點(diǎn)的compile即可:
image.png
結(jié)果:
image.png -
帶有指令的節(jié)點(diǎn)的compile(這里只針對
v-model指令進(jìn)行)
對帶有指令的節(jié)點(diǎn)的節(jié)點(diǎn)進(jìn)行compile需要做如下幾件事:-
獲取節(jié)點(diǎn)的所有屬性名字,然后遍歷,判斷是否存在指令:
image.png
-
- 如果存在指令,就獲取該指令的值和指令類型,例如
v-model=msg就獲取model和msg
image.png
- 如果存在指令,就獲取該指令的值和指令類型,例如
- 在指令集
directive和更新器updater中添加相應(yīng)方法,這里是model方法,并進(jìn)行處理
image.png
image.png
- 在指令集
-
結(jié)果:
image.png
image.png
-
總結(jié)
該篇博客只對Vue中的初始化渲染原理做了介紹,也只是完成了流程圖中Compile的大部分model -> view的綁定,但還未達(dá)成雙向綁定,因此對數(shù)據(jù)的修改并不能對視圖進(jìn)行改變,這就是下一篇博客view -> model的綁定所要介紹的:











































