Vue底層架構(gòu)及其應(yīng)用

閱讀時間大約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渲染。


image.png

我將從如下幾個方面來說明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)。

image.png

組件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)為:

image.png

改變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>
image.png

下面描述下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)格上玲献。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市梯浪,隨后出現(xiàn)的幾起案子捌年,更是在濱河造成了極大的恐慌,老刑警劉巖挂洛,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礼预,死亡現(xiàn)場離奇詭異,居然都是意外死亡虏劲,警方通過查閱死者的電腦和手機托酸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柒巫,“玉大人励堡,你說我怎么就攤上這事”ぬ停” “怎么了应结?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我鹅龄,道長揩慕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任扮休,我火速辦了婚禮迎卤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肛炮。我一直安慰自己止吐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布侨糟。 她就那樣靜靜地躺著碍扔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秕重。 梳的紋絲不亂的頭發(fā)上不同,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音溶耘,去河邊找鬼二拐。 笑死,一個胖子當(dāng)著我的面吹牛凳兵,可吹牛的內(nèi)容都是我干的百新。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼庐扫,長吁一口氣:“原來是場噩夢啊……” “哼饭望!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起形庭,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤铅辞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萨醒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斟珊,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年富纸,在試婚紗的時候發(fā)現(xiàn)自己被綠了囤踩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡晓褪,死狀恐怖高职,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辞州,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布寥粹,位于F島的核電站变过,受9級特大地震影響埃元,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜媚狰,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一岛杀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崭孤,春花似錦类嗤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗤形,卻和暖如春精偿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赋兵。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工笔咽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霹期。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓叶组,卻偏偏與公主長得像,于是被迫代替她去往敵國和親历造。 傳聞我的和親對象是個殘疾皇子甩十,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容