閱讀時間大約16~22min
作者:汪汪
個人主頁:www.zhihu.com/people/wang…
一力试、前言
市面上有很多基于vue的core和compile做出的優(yōu)化開源框架,為非Web場景引入了Vue的能力,因此學(xué)習(xí)成本低,受到廣大開發(fā)者的歡迎,下面大體列一下我所了解到的欠动,有更優(yōu)秀的歡迎大家評論指出
分類 | 技術(shù) |
---|---|
跨平臺native | weex |
關(guān)羽 | 打 |
張飛 | 罵 |
至于提供類Vue開發(fā)體驗的框架就數(shù)不勝數(shù)了,如小程序框架--wepy惑申,
從其他的方面看具伍,github日榜,Vue每天都有過100的star圈驼,足見其火熱程度人芽,這也是為什么大家都爭先恐后的在非web領(lǐng)域提供Vue的支持。那么Vue的底層架構(gòu)及其應(yīng)用就尤為重要了
1绩脆、core
core是Vue的靈魂所在萤厅,正是core實現(xiàn)了通過vnode方式,遞歸生成指定平臺視圖并在數(shù)據(jù)變動時衙伶,自動diff更新視圖祈坠,也正是因為VNode機制,使得core是平臺無關(guān)的矢劲,就算core的功能在于UI渲染。
我將從如下幾個方面來說明core
- 掛載
- 指令
- Vnode----劃重點
- 組件實例vm及vm間的關(guān)系
- nextTick
- Watcher----劃重點
- vnode diff算法----劃重點
- core總結(jié)
<div class="box" @click="onClick">------------------對應(yīng)一個vnode
<p class="content">哈哈</p>-------對應(yīng)一個vnode
<TestComps></TestComps>----------自定義組件同樣對應(yīng)一個vnode
<div></div>-----------------------對應(yīng)一個vnode
</div>
經(jīng)過Vue的compile模塊將生成渲染函數(shù)慌随,執(zhí)行這個渲染函數(shù)就會生成對應(yīng)的vnode結(jié)構(gòu):
//這里我只列出關(guān)鍵的vnode信息
{
tag:'div',
data:{attr:{},staticClass:'box',on:{click:onClick}},
children:[{
tag:'p',
data:{attr:{},staticClass:'content',on:{}},
children:[{
tag:'',
data:{},
text:'哈哈'
}]
},{
tag:'div',
data:{attr:{},on:{}},
},{
tag:'TestComps',
data:{
attr:{},
hook:{
init:fn,
prepatch:fn,
insert:fn,
destroy:fn
}
},
}]
}
最外層的div對應(yīng)一個vnode芬沉,包含三個孩子vnode躺同,注意自定義組件也對應(yīng)一個vnode,不過這個vnode上掛著組件實例
1.4 組件實例vm及vm間的關(guān)系---------劃重點
組件實例其實就是Vue實例對象丸逸,只有自定義組件才會有蹋艺,平臺相關(guān)元素是沒有的,要看懂Vue的core黄刚,明白下面這個關(guān)系很重要∩咏鳎現(xiàn)在,讓我們來直觀感受下:
假定有如下結(jié)構(gòu)的模板憔维,元素上的vnode表示生成的對應(yīng)vnode名稱:
// new Vue的template涛救,對應(yīng)的實例記為vm1
<div vnode1>
<p vnode2></p>
<TestComps vnode3
testAttr="hahha"
@click="clicked"
:username="username"
:password="password"></TestComps>
</div>
// TestComps的template,對應(yīng)的實例記為vm2
<div vnode4>
<span vnode5></span>
<p vnode6></p>
</div>
// 生成的vnode關(guān)系樹為
vnode1={
tag:'div',
children:[vnode2,vnode3]
}
vnode3={
tag:'TestComps',
children:undefined,
parent:undefined
}
vnode4={
tag:'div',
children:[vnode5,vnode6],
parent:vnode3 //這一點關(guān)系很重要
}
// 生成的vm關(guān)系樹為
vm1={
$data:{password: "123456",username: "aliarmo"}, //組件對應(yīng)state
$props:{} //使用組件時候傳下來到模板里面的數(shù)據(jù)
$attrs:{},
$children:[vm2],
$listeners:{}
$options: {
components: {}
parent: undefined //父組件實例
propsData: undefined //使用組件時候傳下來到模板里面的數(shù)據(jù)
_parentVnode: undefined
}
$parent:undefiend //當(dāng)前組件的父組件實例
$refs:{} //當(dāng)前組件里面包含的dom引用
$root:vm1 //根組件實例
$vnode:undefined //組件被引用時候的那個vnode业扒,比如<TestComps></TestComps>
_vnode:vnode1 //當(dāng)前組件模板根元素所對應(yīng)的vnode對象
}
vm2={
$data:{} //組件對應(yīng)state
$props:{password: "123456",username: "aliarmo"} //使用組件時候傳下來到模板里面的數(shù)據(jù)
$attrs:{testAttr:'hahha'},
$children:[],
$listeners:{click:fn}
$options: {
components: {}
parent: vm1 //父組件實例
propsData: {password: "123456",username: "aliarmo"} //使用組件時候傳下來到模板里面的數(shù)據(jù)
_parentVnode: vnode3
}
$parent:vm1 //當(dāng)前組件的父組件實例
$refs:{} //當(dāng)前組件里面包含的dom引用
$root:vm1 //根組件實例
$vnode:vnode3 //組件被引用時候的那個vnode检吆,比如<TestComps></TestComps>
_vnode:vnode4 //當(dāng)前組件模板根元素所對應(yīng)的vnode對象
}
1.5 nextTick
它可以讓我們在下一個事件循環(huán)做一些操作,而非在本次循環(huán)程储,用于異步更新蹭沛,原理在于microtask和macrotask
讓我們來看段代碼:
new Promise(resolve=>{
return 123
}).then(data=>{
console.log('step2',data)
})
console.log('step1')
結(jié)果是先輸出 step1,然后在step2章鲤,resolve的promise是一個microtask摊灭,同步代碼是macrotask
// 在Vue中
this.username='aliarmo' // 可以觸發(fā)更新
this.pwd='123' // 同樣可以觸發(fā)更新
那同時改變兩個state,是否會觸發(fā)兩次更新呢败徊,并不會斟或,因為this.username觸發(fā)更新的回調(diào)會被放入一個通過Promise或者M(jìn)essageChannel實現(xiàn)的microtask中,亦或是setTimeout實現(xiàn)的macrotask集嵌,總之到了下一個事件循環(huán)萝挤。
1.6 Watcher---------劃重點
一個組件對應(yīng)一個watcher吃粒,在掛載組件的時候創(chuàng)建這個觀察者替饿,組件的state,包含data到推,props都是被觀察者凤粗,被觀察者的任何變化會被通知到觀察者酥泛,被觀察者的變動導(dǎo)致觀察者執(zhí)行的動作是vm._update(vm._render(), hydrating),組件重新render生成vnode并patch。
明白這個關(guān)系很重要:觀察者包含對變動做出響應(yīng)的定義嫌拣,一個組件對應(yīng)一個觀察者對應(yīng)組件里面的所有被觀察者柔袁,被觀察者可能被用于其他組件,那么一個被觀察者會對應(yīng)多個觀察者异逐,當(dāng)被觀察者發(fā)生變動時捶索,通知到所有觀察者做出更新響應(yīng)。
組件A的state1發(fā)生了變化灰瞻,那會導(dǎo)致觀察了這個state1的watcher收到變動通知腥例,會導(dǎo)致組件A重新渲染生成新的vnode辅甥,在組件A新vnode和老的vnode patch的過程中,會updateChildrenComponent燎竖,也就是導(dǎo)致子組件B的props被重新設(shè)置一個新值璃弄,因為子組件B是有觀察傳入的state1的,因此會通知到相應(yīng)watcher构回,導(dǎo)致子組件B的更新
整個watcher體系的建立過程:
創(chuàng)建組件實例的時候會對data和props進(jìn)行observer夏块,
對傳入的props進(jìn)行淺遍歷,重新設(shè)定屬性的屬性描述符get和set纤掸,如果props的某個屬性值為對象脐供,那么這個對象在父組件是被深度observe過的,所以props是淺遍歷
observer會深度遍歷data茁肠,對data所包含屬性重新定義患民,即defineReactive,重新設(shè)定屬性描述符的get和set
在mountComponent的時候垦梆,會new Wacther匹颤,當(dāng)前watcher實例會被pushTarget,設(shè)定為目標(biāo)watcher托猩,然后執(zhí)行vm._update(vm._render(), hydrating)印蓖,執(zhí)行render函數(shù)導(dǎo)致屬性的get函數(shù)被調(diào)用,每個屬性會對應(yīng)一個dep實例京腥,在這個時候赦肃,dep實例關(guān)聯(lián)到組件對應(yīng)的watcher,實現(xiàn)依賴收集公浪,關(guān)聯(lián)后popTarget他宛。
如果有子組件,會導(dǎo)致子組件的實例化欠气,重新執(zhí)行上述步驟
state變動響應(yīng)過程:
當(dāng)state變動后厅各,調(diào)用屬性描述符的set函數(shù),dep會通知到關(guān)聯(lián)的watcher進(jìn)入到nextTick任務(wù)里面预柒,這個watcher實例的run函數(shù)包含vm._update(vm._render(), hydrating)队塘,執(zhí)行這個run函數(shù),導(dǎo)致重新生成vnode宜鸯,進(jìn)行patch憔古,經(jīng)過diff,達(dá)到更新UI目的
父組件state變化如何導(dǎo)致子組件也發(fā)生變化淋袖?
父組件state更新后鸿市,會導(dǎo)致渲染函數(shù)重新執(zhí)行,生成新的vnode适贸,在oldVnode和newVnode patch的過程中灸芳,如果遇到的是組件vnode涝桅,會updateChildrenComponent拜姿,這里面做的操作就是更新子組件的props烙样,因為子組件是有監(jiān)聽props屬性的變動的,導(dǎo)致子組件re-render
父組件傳入一個對象給子組件蕊肥,子組件改變傳入的對象props谒获,父組件又是如何被更新到的?
大前提:如果父組件傳給子組件的props中有對象壁却,那么子組件接收到的是這個對象的引用批狱。也就是ParentComps中的this.person和SubComps中的this.person指向同一個對象
// 假定父組件傳person對象給子組件SubComps
Vue.component('ParentComps',{
data(){
return {
person:{
username:'aliarmo',
pwd:123
}
}
},
template:`
<div>
<p>{{person.username}}</p>
<SubComps :person="person" />
</div>
`
})
現(xiàn)在我們在SubComps里面,更新person對象的某個屬性,如:this.person.username='wmy' 這樣會導(dǎo)致ParentComps和SubComps的更新展东,為什么呢赔硫?
因為Vue在ParentComps中會深度遞歸觀察對象的每個屬性,在第一次執(zhí)行ParentComps的render的時候盐肃,綁定ParentComps的Watcher爪膊,傳入到SubComps后,不會對傳入的對象在進(jìn)行觀察砸王,在第一次執(zhí)行SubComps的render的時候推盛,會綁定到SubComps的Watcher,因此當(dāng)SubComps改變了this.person.username的值谦铃,會通知到兩個Watcher耘成,導(dǎo)致更新。這很好的解釋了憑空在傳入的props屬性對象上掛載新的屬性不觸發(fā)渲染驹闰,因為傳入的props屬性對象是在父組件被觀察的瘪菌。
1.7 vnode diff算法---------劃重點
當(dāng)組件的state發(fā)生變化,重新執(zhí)行渲染函數(shù)生成新的vnode嘹朗,然后將新生成的vnode與老的vnode進(jìn)行對比师妙,以最小的代價更新原有視圖。diff算法的原理是通過移動骡显、新增疆栏、刪除和替換oldChildrenVnodes對應(yīng)的結(jié)構(gòu)來生成newChildrenVnodes對應(yīng)的結(jié)構(gòu),并且每個老的元素只能被復(fù)用一次惫谤,老元素最終的位置取決于當(dāng)前新的vnode壁顶。要明確傳入diff算法的是兩個sameVnode的孩子節(jié)點,從兩者的開頭和結(jié)尾位置溜歪,同時往中間靠若专,直到兩者中的一個到達(dá)中間。
PS:oldChildrenVnodes表示老的孩子vnode節(jié)點集合蝴猪,newChildrenVnodes表示state變化后生成的新的孩子vnode節(jié)點集合
說這個算法之前调衰,先得明白如何判斷兩個vnode為sameVnode膊爪,我只大體列一下:
vnode的key值相等,例如<Comps1 key="key1" />, <Comps2 key="key2" />嚎莉,key值就不相等, <Comps1 key="key1" />, <Comps2 key="key1" />, key值就是相等的, <div></div>,<p></p>,這兩個的key值是undefined米酬,key值相等,這個是sameVnode的大前提趋箩。
vnode的tag相同赃额,都是注釋或者都不是注釋,同時定義或未定義data叫确,標(biāo)簽為input則type必須相同跳芳,還有些其他的條件跟我們不太相關(guān)就不列出來了。
整個vnode diff流程
大前提竹勉,要看懂這個vnode diff飞盆,務(wù)必先明白vnode是啥,如何生成的次乓,vnode與elm的關(guān)系吓歇,詳情請看上面的vnode概念
如果兩個vnode是sameVnode,則進(jìn)行patch vnode
patch vnode過程
(1)首先vnode的elm指向oldVnode的elm
?(2)使用vnode的數(shù)據(jù)更新elm的attr檬输,class照瘾,style,domProps丧慈,events等
?(3)如果vnode是文本節(jié)點析命,則直接設(shè)置elm的text,結(jié)束
?(4)如果vnode是非文本節(jié)點&&有孩子&&oldVnode沒有孩子逃默,則elm直接append
?(5)如果vnode是非文本節(jié)點&&沒有孩子&&oldVnode有孩子,則直接移除elm的孩子節(jié)點
?(6)如果非文本節(jié)點&&都有孩子節(jié)點鹃愤,則updateChildren,進(jìn)入diff 算法完域,前面5個步驟排除了不能進(jìn)行diff情況
diff 算法软吐,這里以web平臺為例
這里還有強調(diào)下,傳入diff算法的是兩個sameVnode的孩子節(jié)點吟税,那么如何用newChildrenVnodes替換oldChildrenVnodes凹耙,最簡單的方式莫過于,遍歷newChildrenVnodes肠仪,直接重新生成這個html片段肖抱,皆大歡喜。但是這樣做會 不斷的createElement异旧,對性能有影響意述,于是前輩們就想出了這個diff算法。
(1)取兩者最左邊的節(jié)點,判斷是否為sameVnode荤崇,如果是則進(jìn)行上述的第二步patch vnode過程拌屏,整個流程走完后,此時elm的class术荤,style倚喂,events等已經(jīng)更新了,elm的children結(jié)構(gòu)也通過前面說的整個流程得到了更新喜每,這時候就看是否需要移動這個elm了务唐,因為都是孩子的最左邊節(jié)點雳攘,因此位置不變带兜,最左邊節(jié)點位置向前移動一步
(2)如果不是(1)所述case,取兩者最右邊的節(jié)點吨灭,跟(1)的判定流程一樣刚照,不過是最右邊節(jié)點位置向前移動一步
(3)如果不是(1)(2)所述case,取oldChildrenVnodes最左邊節(jié)點和newChildrenVnodes最右邊節(jié)點喧兄,跟(1)的判定流程一樣无畔,不過,elm的位置需要移動到oldVnode最右邊elm的右邊吠冤,因為vnode取的是最右邊節(jié)點浑彰,如果與oldVnode的最右邊節(jié)點是sameVnode的話,位置是不用改變的拯辙,因此newChildrenVnodes的最右節(jié)點和oldChildrenVnodes的最右節(jié)點位置是對應(yīng)的郭变,但由于是復(fù)用的oldChildrenVnodes的最左邊節(jié)點,oldChildrenVnodes最右邊節(jié)點還沒有被復(fù)用涯保,因此不能替換掉诉濒,所以移動到oldChildrenVnodes最右邊elm的右邊。然后oldChildrenVnodes最左邊節(jié)點位置向前移動一步夕春,newChildrenVnodes最右邊節(jié)點位置向前移動一步
(4)如果不是(1)(2)(3)所述case未荒,取oldChildrenVnodes最右邊節(jié)點和newChildrenVnodes最左邊節(jié)點,跟(1)的判定流程一樣及志,不過片排,elm的位置需要移動到oldChildrenVnodes最左邊elm的左邊,因為vnode取的是最左邊節(jié)點速侈,如果與oldChildrenVnodes的最左邊節(jié)點是sameVnode的話率寡,位置是不用改變的,因此newChildrenVnodes的最左節(jié)點和oldChildrenVnodes的最左節(jié)點位置是對應(yīng)的锌畸,但由于是復(fù)用的oldChildrenVnodes的最右邊節(jié)點勇劣,oldChildrenVnodes最左邊節(jié)點還沒有被復(fù)用,因此不能替換掉,所以移動到oldChildrenVnodes最左邊elm的左邊比默。然后oldChildrenVnodes最右邊節(jié)點位置向前移動一步幻捏,newChildrenVnodes最左邊節(jié)點位置向前移動一步
(5)如果不是(1)(2)(3)(4)所述case,在oldChildrenVnodes中尋找與newChildrenVnodes最左邊節(jié)點是sameVnode的oldVnode命咐,如果沒有找到篡九,則用這個新的vnode創(chuàng)建一個新element,插入位置如后所述醋奠,如果找到了榛臼,則跟(1)的判定流程一樣,不過插入的位置是oldChildrenVnodes的最左邊節(jié)點的左邊窜司,因為如果newChildrenVnodes最左邊節(jié)點與oldChildrenVnodes最左邊節(jié)點是sameVnode的話沛善,位置是不用變的,并且復(fù)用的是oldChildrenVnodes中找到的oldVNode的elm塞祈。被復(fù)用過的oldVnode后面不會再被取出來金刁。然后newChildrenVnodes最左邊節(jié)點位置向前移動一步
(6)經(jīng)過上述步驟,oldChildrenVnodes或者newChildrenVnodes的最左節(jié)點與最右節(jié)點重合议薪,退出循壞
(7)如果是oldChildrenVnodes的最左節(jié)點與最右節(jié)點先重合尤蛮,說明newChildrenVNodes還有節(jié)點沒有被插入,遞歸創(chuàng)建這些節(jié)點對應(yīng)元素斯议,然后插入到oldChildrenVnodes的最左節(jié)點的右邊或者最右節(jié)點的左邊产捞,因為是從兩者的開始和結(jié)束位置向中間靠攏,想想哼御,如果newChildrenVNodes剩余的第一個節(jié)點與oldChildrenVnodes的最左邊節(jié)點為sameVnode的話坯临,位置是不用變的
(8)如果是newChildrenVnodes的最左節(jié)點與最右節(jié)點先重合,說明oldChildrenVnodes中有一段結(jié)構(gòu)沒有被復(fù)用艇搀,開始和結(jié)束位置向中間靠攏尿扯,因此沒有被復(fù)用的位置是oldChildrenVnodes的最左邊和最右邊之間節(jié)點,刪除節(jié)點對應(yīng)的elm即可焰雕。
舉個例子來描述下具體的diff過程(web平臺):
// 有Vue模板如下
<div> ------ oldVnode1衷笋,newVnode1,element1
<span v-if="isShow1"></span> -------oldVnode2矩屁,newVnode2辟宗,element2
<div :key="key"></div> -------oldVnode3,newVnode3吝秕,element3
<p></p> -------oldVnode4泊脐,newVnode4,element4
<div v-if="isShow2"></div> -------oldVnode5烁峭,newVnode5容客,element5
</div>
// 如果 isShow1=true秕铛,isShow2=true,key="aliarmo"那么模板將會渲染成如下:
<div>
<span></span>--------------element2
<div key="aliarmo"></div>----------element3
<p></p>-------------element4
<div></div>----------element5
</div>
// 改變state缩挑,isShow1=false但两,isShow2=true,key="wmy"供置,那么模板將會渲染成如下:
<div>
<div key="wmy"></div>------------element6
<p></p>-------------------element4
<div></div>---------element5
</div>
那么谨湘,改變state后的dom結(jié)構(gòu)是如何生成的?
如上圖芥丧,在isShow1=true紧阔,isShow2=true,key="aliarmo"條件下续担,生成的vnode結(jié)構(gòu)是:
oldVnode1擅耽,oldVnodeChildren=[oldVnode2,oldVnode3,oldVnode4,oldVnode5]
對應(yīng)的dom結(jié)構(gòu)為:
改變state isShow1=false,isShow2=true赤拒,key="wmy"后秫筏,生成的新vnode結(jié)構(gòu)是
newVnode1,newVnodeChildren=[newVnode3,newVnode4,newVnode5]
最左邊兩個新老vnode對比挎挖,也就是oldVnode2,newVnode3航夺,不是sameVnode蕉朵,
那最右邊兩個新老vnode對比,也就是oldVnode5阳掐,newVnode5始衅,是sameVnode,不用移動原來的Element5所在位置缭保,原有dom結(jié)構(gòu)未發(fā)生變化汛闸,
最左邊兩個新老vnode對比,也就是oldVnode2艺骂,newVnode3诸老,不是sameVnode,
那最右邊兩個新老vnode對比钳恕,也就是oldVnode4别伏,newVnode4,是sameVnode忧额,不用移動原來的Element4所在位置厘肮,原有dom結(jié)構(gòu)未發(fā)生變化,
最左邊兩個新老vnode對比睦番,也就是oldVnode2类茂,newVnode3耍属,不是sameVnode,
那最右邊兩個新老vnode對比巩检,也就是oldVnode3恬涧,newVnode3,由于key值不同碴巾,不是sameVnode溯捆,
當(dāng)前最左邊和最右邊對比,oldVnode2厦瓢,newVnode3提揍,不是sameVnode
當(dāng)前最右邊和最左邊對比,oldVnode5煮仇,newVnode3劳跃,不是sameVnode
在遍歷oldVnodeChildren,尋找與newVnode3為sameVnode的oldVnode浙垫,沒有找到刨仑,則用newVnode3創(chuàng)建一個新的元素Element6,插入到當(dāng)前oldVnode2所對應(yīng)元素的最左邊夹姥,dom結(jié)構(gòu)發(fā)生變化
newVnodeChildren兩頭重合杉武,退出循環(huán),刪除剩余未被復(fù)用元素Element2辙售,Element3
1.8 core總結(jié)
現(xiàn)在我們終于可以理一下轻抱,從new Vue()開始,core里面發(fā)生了些什么
new Vue()或者new自定義組件構(gòu)造函數(shù)(繼承自Vue)
初始化旦部,props祈搜,methods,computed士八,data容燕,watch,并給state加上Observe婚度,調(diào)用生命周期created
開始mount組件蘸秘,mount之前確保render函數(shù)的生成
new Watcher,導(dǎo)致render和patch陕见,注意一個watcher對應(yīng)一個組件秘血,watcher對變化的響應(yīng)是重新執(zhí)行render生成vnode進(jìn)行patch
render在當(dāng)前組件上下文(組件實例)執(zhí)行,生成對應(yīng)的vnode結(jié)構(gòu)
如果沒有oldVnode评甜,那patch就是深度遍歷vnode灰粮,生成具體的平臺元素,給具體的平臺元素添加屬性和綁定事件忍坷,調(diào)用自定義指令提供的鉤子函數(shù)粘舟,并append到已存在的元素上熔脂,在遍歷的過程中,如果遇到的是自定義組件柑肴,則從步驟1開始重復(fù)
如果有oldVnode霞揉,那patch就是利用vnode diff算法在原有的平臺元素上進(jìn)行修修補補,不到萬不得已不創(chuàng)建新的平臺元素
state發(fā)生變化晰骑,通知到state所在組件對應(yīng)的watcher适秩,重新執(zhí)行render生成vnode進(jìn)行patch,也就是回到步驟4
2硕舆、compiler
Vue的compiler部分負(fù)責(zé)對template的編譯秽荞,生成render和staticRender函數(shù),編譯一次永久使用抚官,所以一般我們在構(gòu)建的時候就做了這件事情扬跋,以提高頁面性能。執(zhí)行render和staticRender函數(shù)可以生成VNode凌节,從而為core提供這一層抽象钦听。
template ==》 AST ==》 遞歸ATS生成render和staticRender ==》VNode
(1)template轉(zhuǎn)化成AST過程
先讓我們來直觀感受下AST,它描述了下面的template結(jié)構(gòu)
// Vue模板
let template = `
<div class="Test" :class="classObj" v-show="isShow">
{{username}}:{{password}}
<div>
<span>hahhahahha</span>
</div>
<div v-if="isVisiable" @click="onClick"></div>
<div v-for="item in items">{{item}}</div>
</div>
下面描述下template轉(zhuǎn)為AST的簡要過程:
如果template是以<開始的字符串倍奢,則判斷是評論朴上,還是Doctype還是結(jié)束標(biāo)簽,或者是開始標(biāo)簽娱挨,這里只說處理開始和結(jié)束標(biāo)簽余指。
(1)如果是開始標(biāo)簽,則處理類似于下面字符串
<div class="Test" :class="classObj" v-show="isShow">
復(fù)制代碼通過正則可以很容易解析出tag跷坝,所有屬性列表,再對屬性列表進(jìn)行分類碉碉,分別解析出v-if,v-for等指令柴钻,事件,特殊屬性等垢粮,template去除被解析的部分贴届,回到步驟1
(2)如果是結(jié)束標(biāo)簽,則處理類似于下面字符串蜡吧,同樣template去除被解析的部分毫蚓,回到步驟1
</div>
復(fù)制代碼
如果不是第一種情況,說明是字符串昔善,處理類似于下面的插值字符串或者純文本元潘,同樣template去除被解析的部分,回到步驟1
{{username}}:{{password}} 或者 用戶名:密碼
復(fù)制代碼
如果template為空君仆,解析結(jié)束
(2)AST生成render和staticRender
主要是遍歷ast(有興趣的同學(xué)可以自己體驗下翩概,如:遍歷AST生成還原上述模板牲距,相信會有不一樣的體驗),根據(jù)每個節(jié)點的屬性拼接渲染函數(shù)的字符串钥庇,如:模板中有v-if="isVisiable"牍鞠,那么AST中這個節(jié)點就會有一個if屬性,這樣评姨,在創(chuàng)建這個節(jié)點對應(yīng)的VNode的時候难述,就會有
(isVisiable) ? _c('div') : _e()
復(fù)制代碼在with的作用下,isVisiable 的值決定了VNode是否生成吐句。當(dāng)然胁后,對于一些指令,在編譯時是處理不了的蕴侧,會在生成VNode的時候掛載在VNode上择同,解析VNode時再進(jìn)行進(jìn)一步處理,比如v-show净宵,v-on敲才。
下面是上面模板生成的render和staticRender:
// render函數(shù)
(function anonymous() {
with (this) {
return _c('div', {
directives: [{
name: "show",
rawName: "v-show",
value: (isShow),
expression: "isShow"
}],
staticClass: "Test",
class: classObj
}, [_v("\n " + _s(username) + ":" + _s(password) + "\n "), _m(0), _v(" "), (isVisiable) ? _c('div', {
on: {
"click": onClick
}
}) : _e(), _v(" "), _l((items), function(item) {
return _c('div', [_v(_s(item))])
})], 2)
}
}
)
// staticRender
(function anonymous() {
with (this) {
return _c('div', [_c('span', [_v("hahhahahha")])])
}
}
)
其中this是組件實例,_c择葡、_v分別用于創(chuàng)建VNode和字符串紧武,像username和password是在定義組件時候傳入的state并被掛載在this上。
3敏储、platform
platform模塊與具體平臺相關(guān)阻星,我們可以在這里定義平臺相關(guān)接口傳入runtime和compile,以實現(xiàn)具體平臺的定制化已添,因此為其他平臺帶來Vue能力妥箕,大部分工作在這里。
需要傳入runtime的是如何創(chuàng)建具體的平臺元素更舞,平臺元素之間的關(guān)系以及如何append畦幢,insert,remove平臺元素等缆蝉,元素生成后需要進(jìn)行的屬性宇葱,事件監(jiān)聽等。拿web平臺舉例刊头,我們需要傳入document.createElement黍瞧,document.createTextNode,遍歷vnode的時候生成HTML元素原杂;掛載時需要的insertBefore印颤;state發(fā)生變化導(dǎo)致vnode diff時的remove,append等污尉。還有生成HTML元素后膀哲,用setAttribute和removeAttribute操作屬性往产;addEventListener和removeEventListener進(jìn)行事件監(jiān)聽;提供一些有利于web平臺使用的自定義組件和指令等等
需要傳入compile的是對某些特殊屬性或者指令在編譯時的處理某宪。如web平臺仿村,需要對class,style兴喂,model的特殊處理蔼囊,以區(qū)別于一般的HTML屬性;提供web平臺專用指令衣迷,v-html(編譯后其實是綁定元素的innerHTML)畏鼓,v-text(編譯后其實是綁定元素的textContent),v-model壶谒,這些指令依賴于具體的平臺元素云矫。
三、應(yīng)用
說了這么多汗菜,最終目的是為了復(fù)用Vue的core和compile让禀,以期在其他的平臺上帶來Vue或者類Vue的開發(fā)體驗,前面也說了很多復(fù)用成功的例子陨界,如果你要為某一平臺帶來Vue的開發(fā)體驗巡揍,可以進(jìn)行參考。大前端概念下菌瘪,指不定那天腮敌,汽車顯示屏,智能手表等終端界面就可以用Vue來進(jìn)行開發(fā)俏扩,爽歪歪糜工。那么,如何復(fù)用呢录淡?當(dāng)然我只說必選的啤斗,你可以定制更多更復(fù)雜的功能方便具體平臺使用。
定義vnode生成具體平臺元素所需要的nodeOps赁咙,也就是元素的增刪改查,對于web來說nodeOps是要創(chuàng)建免钻,移動真正的dom對象彼水,如果是其他平臺,可自行定義這些元素的操作方法极舔;
定義vnode生成具體平臺元素所需要的modules凤覆,對于web來說,modules是用來操作dom屬性的方法拆魏;
具體平臺需要定義一個自己的$mount方法給Vue盯桦,掛載組件到已存在的元素上慈俯;
還有一些方法,如isReservedTag拥峦,是否為保留的標(biāo)簽名贴膘,自定義組件名稱不能與這些相同;mustUseProp略号,判定元素的某個屬性必須要跟組件state綁定刑峡,不能綁定一個常量等等;
四玄柠、總結(jié)
軟件行業(yè)有一句名言突梦,沒有什么問題是添加一層抽象層不能解決的,如果有那就兩層羽利,Vue的設(shè)計正是如此(可能Vue也是借鑒別人的)宫患,compile的AST抽象層銜接了模板語法和render函數(shù),經(jīng)過VNode這個抽象層讓core剝離了具體平臺这弧。
這篇文章的最終目的是為了讓大家了解到復(fù)用Vue源碼的core和compile進(jìn)行二次開發(fā)娃闲,可以做到為具體平臺帶來Vue或者類Vue的開發(fā)體驗。當(dāng)然当宴,這只是一種思路畜吊,說不定哪天,Vue風(fēng)格開發(fā)體驗失寵了户矢,那么我們也可以把這種思路應(yīng)用到新開發(fā)風(fēng)格上玲献。