Vue底層架構及其應用

騰訊IVWEB團隊發表於2019-05-30

閱讀時間大約16~22min
作者:汪汪
個人主頁:www.zhihu.com/people/wang…

一、前言

市面上有很多基於vue的core和compile做出的優化開源框架,為非Web場景引入了Vue的能力,因此學習成本低,受到廣大開發者的歡迎,下面大體列一下我所瞭解到的,有更優秀的歡迎大家評論指出

分類 技術
跨平臺native weex
小程式 mpvue
服務端渲染 Vue SSR
小程式多端統一框架 uni-app

至於提供類Vue開發體驗的框架就數不勝數了,如小程式框架--wepy,

從其他的方面看,github日榜,Vue每天都有過100的star,足見其火熱程度,這也是為什麼大家都爭先恐後的在非web領域提供Vue的支援。那麼Vue的底層架構及其應用就尤為重要了

二、Vue底層架構

瞭解Vue的底層架構,是為非web領域提供Vue能力的大前提。Vue核心分為三大塊:core,compiler,platform,下面分別介紹其原理及帶來的能力。

1、core

core是Vue的靈魂所在,正是core實現了通過vnode方式,遞迴生成指定平臺檢視並在資料變動時,自動diff更新檢視,也正是因為VNode機制,使得core是平臺無關的,就算core的功能在於UI渲染。

Vue底層架構及其應用

我將從如下幾個方面來說明core

  • 掛載
  • 指令
  • Vnode----劃重點
  • 元件例項vm及vm間的關係
  • nextTick
  • Watcher----劃重點
  • vnode diff演算法----劃重點
  • core總結

1.1 掛載

將vnode生成的具體平臺元素append到已知節點上。我們拿web平臺舉例,用vnode通過document.createElement生成dom,然後在append到文件樹中某個節點上。後面我們也會經常說到掛載元件,它指的就是執行元件對應render生成vnode,然後遍歷vnode生成具體平臺元素,元件的根節點元素會被append到父元素上。

1.2 指令

指令在Vue中是具有特定含義的屬性,指令分兩類,一類是編譯時處理,在生成的render函式上體現,如:v-if,v-for,另外一類是執行時使用,更多的是對生成的具體平臺元素操作,web平臺的話就是對dom的操作

1.3 VNode---------劃重點

vnode是虛擬node節點,是具體平臺元素物件的進一步抽象(簡化版),每一個平臺元素對應一個vnode,可通過vnode結構完整還原具體平臺元素結構。 下面以web平臺來解釋vnode。對於web,假定有如下結構:

<div class="box" @click="onClick">------------------對應一個vnode
    <p class="content">哈哈</p>-------對應一個vnode
    <TestComps></TestComps>----------自定義元件同樣對應一個vnode
    <div></div>-----------------------對應一個vnode
</div>
複製程式碼

經過Vue的compile模組將生成渲染函式,執行這個渲染函式就會生成對應的vnode結構:

//這裡我只列出關鍵的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對應一個vnode,包含三個孩子vnode,注意自定義元件也對應一個vnode,不過這個vnode上掛著元件例項

1.4 元件例項vm及vm間的關係---------劃重點

元件例項其實就是Vue例項物件,只有自定義元件才會有,平臺相關元素是沒有的,要看懂Vue的core,明白下面這個關係很重要。現在,讓我們來直觀感受下:

假定有如下結構的模板,元素上的vnode表示生成的對應vnode名稱:

// new Vue的template,對應的例項記為vm1
<div vnode1>
  <p vnode2></p>
  <TestComps vnode3
             testAttr="hahha"
             @click="clicked"
             :username="username"
             :password="password"></TestComps>
</div>
複製程式碼
// TestComps的template,對應的例項記為vm2
<div vnode4>
  <span vnode5></span>
  <p vnode6></p>
</div>
複製程式碼
// 生成的vnode關係樹為
vnode1={
  tag:'div',
  children:[vnode2,vnode3]
}
vnode3={
  tag:'TestComps',
  children:undefined,
  parent:undefined
}
vnode4={
  tag:'div',
  children:[vnode5,vnode6],
  parent:vnode3             //這一點關係很重要
}
複製程式碼
// 生成的vm關係樹為
vm1={
  $data:{password: "123456",username: "aliarmo"}, //元件對應state
  $props:{} //使用元件時候傳下來到模板裡面的資料
  $attrs:{},
  $children:[vm2],             
  $listeners:{}
  $options: {
    components: {}
    parent: undefined   //父元件例項
    propsData: undefined    //使用元件時候傳下來到模板裡面的資料
    _parentVnode: undefined 
  }
  $parent:undefiend               //當前元件的父元件例項
  $refs:{}                 //當前元件裡面包含的dom引用
  $root:vm1                 //根元件例項
  $vnode:undefined                 //元件被引用時候的那個vnode,比如<TestComps></TestComps>
  _vnode:vnode1       //當前元件模板根元素所對應的vnode物件
}

vm2={
  $data:{} //元件對應state
	$props:{password: "123456",username: "aliarmo"} //使用元件時候傳下來到模板裡面的資料
  $attrs:{testAttr:'hahha'},
  $children:[],             
  $listeners:{click:fn}
  $options: {
    components: {}
    parent: vm1   //父元件例項
    propsData: {password: "123456",username: "aliarmo"}    //使用元件時候傳下來到模板裡面的資料
    _parentVnode: vnode3 
  }
  $parent:vm1               //當前元件的父元件例項
  $refs:{}                 //當前元件裡面包含的dom引用
  $root:vm1                 //根元件例項
  $vnode:vnode3                 //元件被引用時候的那個vnode,比如<TestComps></TestComps>
  _vnode:vnode4       //當前元件模板根元素所對應的vnode物件
}
複製程式碼

1.5 nextTick

它可以讓我們在下一個事件迴圈做一些操作,而非在本次迴圈,用於非同步更新,原理在於microtask和macrotask

讓我們來看段程式碼:

new Promise(resolve=>{
  return 123
}).then(data=>{
  console.log('step2',data)
})
console.log('step1')
複製程式碼

結果是先輸出 step1,然後在step2,resolve的promise是一個microtask,同步程式碼是macrotask

// 在Vue中
this.username='aliarmo' // 可以觸發更新
this.pwd='123'          // 同樣可以觸發更新
複製程式碼

那同時改變兩個state,是否會觸發兩次更新呢,並不會,因為this.username觸發更新的回撥會被放入一個通過Promise或者MessageChannel實現的microtask中,亦或是setTimeout實現的macrotask,總之到了下一個事件迴圈。

1.6 Watcher---------劃重點

一個元件對應一個watcher,在掛載元件的時候建立這個觀察者,元件的state,包含data,props都是被觀察者,被觀察者的任何變化會被通知到觀察者,被觀察者的變動導致觀察者執行的動作是vm._update(vm._render(), hydrating),元件重新render生成vnode並patch。

明白這個關係很重要:觀察者包含對變動做出響應的定義,一個元件對應一個觀察者對應元件裡面的所有被觀察者,被觀察者可能被用於其他元件,那麼一個被觀察者會對應多個觀察者,當被觀察者發生變動時,通知到所有觀察者做出更新響應。

Vue底層架構及其應用

元件A的state1發生了變化,那會導致觀察了這個state1的watcher收到變動通知,會導致元件A重新渲染生成新的vnode,在元件A新vnode和老的vnode patch的過程中,會updateChildrenComponent,也就是導致子元件B的props被重新設定一個新值,因為子元件B是有觀察傳入的state1的,因此會通知到相應watcher,導致子元件B的更新

整個watcher體系的建立過程

  1. 建立元件例項的時候會對data和props進行observer,
  2. 對傳入的props進行淺遍歷,重新設定屬性的屬性描述符get和set,如果props的某個屬性值為物件,那麼這個物件在父元件是被深度observe過的,所以props是淺遍歷
  3. observer會深度遍歷data,對data所包含屬性重新定義,即defineReactive,重新設定屬性描述符的get和set
  4. 在mountComponent的時候,會new Wacther,當前watcher例項會被pushTarget,設定為目標watcher,然後執行vm._update(vm._render(), hydrating),執行render函式導致屬性的get函式被呼叫,每個屬性會對應一個dep例項,在這個時候,dep例項關聯到元件對應的watcher,實現依賴收集,關聯後popTarget。
  5. 如果有子元件,會導致子元件的例項化,重新執行上述步驟

state變動響應過程

  1. 當state變動後,呼叫屬性描述符的set函式,dep會通知到關聯的watcher進入到nextTick任務裡面,這個watcher例項的run函式包含vm._update(vm._render(), hydrating),執行這個run函式,導致重新生成vnode,進行patch,經過diff,達到更新UI目的

父元件state變化如何導致子元件也發生變化?

父元件state更新後,會導致渲染函式重新執行,生成新的vnode,在oldVnode和newVnode patch的過程中,如果遇到的是元件vnode,會updateChildrenComponent,這裡面做的操作就是更新子元件的props,因為子元件是有監聽props屬性的變動的,導致子元件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>
  `
})
複製程式碼

現在我們在SubComps裡面,更新person物件的某個屬性,如:this.person.username='wmy' 這樣會導致ParentComps和SubComps的更新,為什麼呢?

因為Vue在ParentComps中會深度遞迴觀察物件的每個屬性,在第一次執行ParentComps的render的時候,繫結ParentComps的Watcher,傳入到SubComps後,不會對傳入的物件在進行觀察,在第一次執行SubComps的render的時候,會繫結到SubComps的Watcher,因此當SubComps改變了this.person.username的值,會通知到兩個Watcher,導致更新。這很好的解釋了憑空在傳入的props屬性物件上掛載新的屬性不觸發渲染,因為傳入的props屬性物件是在父元件被觀察的。

1.7 vnode diff演算法---------劃重點

當元件的state發生變化,重新執行渲染函式生成新的vnode,然後將新生成的vnode與老的vnode進行對比,以最小的代價更新原有檢視。diff演算法的原理是通過移動、新增、刪除和替換oldChildrenVnodes對應的結構來生成newChildrenVnodes對應的結構,並且每個老的元素只能被複用一次,老元素最終的位置取決於當前新的vnode。要明確傳入diff演算法的是兩個sameVnode的孩子節點,從兩者的開頭和結尾位置,同時往中間靠,直到兩者中的一個到達中間。

PS:oldChildrenVnodes表示老的孩子vnode節點集合,newChildrenVnodes表示state變化後生成的新的孩子vnode節點集合

說這個演算法之前,先得明白如何判斷兩個vnode為sameVnode,我只大體列一下:

  1. vnode的key值相等,例如<Comps1 key="key1" />, <Comps2 key="key2" />,key值就不相等, <Comps1 key="key1" />, <Comps2 key="key1" />, key值就是相等的, <div></div>,<p></p>,這兩個的key值是undefined,key值相等,這個是sameVnode的大前提。
  2. vnode的tag相同,都是註釋或者都不是註釋,同時定義或未定義data,標籤為input則type必須相同,還有些其他的條件跟我們不太相關就不列出來了。

整個vnode diff流程

大前提,要看懂這個vnode diff,務必先明白vnode是啥,如何生成的,vnode與elm的關係,詳情請看上面的vnode概念

  1. 如果兩個vnode是sameVnode,則進行patch vnode
  2. patch vnode過程

 (1)首先vnode的elm指向oldVnode的elm

 (2)使用vnode的資料更新elm的attr,class,style,domProps,events等

 (3)如果vnode是文字節點,則直接設定elm的text,結束

 (4)如果vnode是非文字節點&&有孩子&&oldVnode沒有孩子,則elm直接append

 (5)如果vnode是非文字節點&&沒有孩子&&oldVnode有孩子,則直接移除elm的孩子節點

 (6)如果非文字節點&&都有孩子節點,則updateChildren,進入diff 演算法,前面5個步驟排除了不能進行diff情況

  1. diff 演算法,這裡以web平臺為例

這裡還有強調下,傳入diff演算法的是兩個sameVnode的孩子節點,那麼如何用newChildrenVnodes替換oldChildrenVnodes,最簡單的方式莫過於,遍歷newChildrenVnodes,直接重新生成這個html片段,皆大歡喜。但是這樣做會 不斷的createElement,對效能有影響,於是前輩們就想出了這個diff演算法。

(1)取兩者最左邊的節點,判斷是否為sameVnode,如果是則進行上述的第二步patch vnode過程,整個流程走完後,此時elm的class,style,events等已經更新了,elm的children結構也通過前面說的整個流程得到了更新,這時候就看是否需要移動這個elm了,因為都是孩子的最左邊節點,因此位置不變,最左邊節點位置向前移動一步

(2)如果不是(1)所述case,取兩者最右邊的節點,跟(1)的判定流程一樣,不過是最右邊節點位置向前移動一步

(3)如果不是(1)(2)所述case,取oldChildrenVnodes最左邊節點和newChildrenVnodes最右邊節點,跟(1)的判定流程一樣,不過,elm的位置需要移動到oldVnode最右邊elm的右邊,因為vnode取的是最右邊節點,如果與oldVnode的最右邊節點是sameVnode的話,位置是不用改變的,因此newChildrenVnodes的最右節點和oldChildrenVnodes的最右節點位置是對應的,但由於是複用的oldChildrenVnodes的最左邊節點,oldChildrenVnodes最右邊節點還沒有被複用,因此不能替換掉,所以移動到oldChildrenVnodes最右邊elm的右邊。然後oldChildrenVnodes最左邊節點位置向前移動一步,newChildrenVnodes最右邊節點位置向前移動一步

(4)如果不是(1)(2)(3)所述case,取oldChildrenVnodes最右邊節點和newChildrenVnodes最左邊節點,跟(1)的判定流程一樣,不過,elm的位置需要移動到oldChildrenVnodes最左邊elm的左邊,因為vnode取的是最左邊節點,如果與oldChildrenVnodes的最左邊節點是sameVnode的話,位置是不用改變的,因此newChildrenVnodes的最左節點和oldChildrenVnodes的最左節點位置是對應的,但由於是複用的oldChildrenVnodes的最右邊節點,oldChildrenVnodes最左邊節點還沒有被複用,因此不能替換掉,所以移動到oldChildrenVnodes最左邊elm的左邊。然後oldChildrenVnodes最右邊節點位置向前移動一步,newChildrenVnodes最左邊節點位置向前移動一步

(5)如果不是(1)(2)(3)(4)所述case,在oldChildrenVnodes中尋找與newChildrenVnodes最左邊節點是sameVnode的oldVnode,如果沒有找到,則用這個新的vnode建立一個新element,插入位置如後所述,如果找到了,則跟(1)的判定流程一樣,不過插入的位置是oldChildrenVnodes的最左邊節點的左邊,因為如果newChildrenVnodes最左邊節點與oldChildrenVnodes最左邊節點是sameVnode的話,位置是不用變的,並且複用的是oldChildrenVnodes中找到的oldVNode的elm。被複用過的oldVnode後面不會再被取出來。然後newChildrenVnodes最左邊節點位置向前移動一步

(6)經過上述步驟,oldChildrenVnodes或者newChildrenVnodes的最左節點與最右節點重合,退出循壞

(7)如果是oldChildrenVnodes的最左節點與最右節點先重合,說明newChildrenVNodes還有節點沒有被插入,遞迴建立這些節點對應元素,然後插入到oldChildrenVnodes的最左節點的右邊或者最右節點的左邊,因為是從兩者的開始和結束位置向中間靠攏,想想,如果newChildrenVNodes剩餘的第一個節點與oldChildrenVnodes的最左邊節點為sameVnode的話,位置是不用變的

(8)如果是newChildrenVnodes的最左節點與最右節點先重合,說明oldChildrenVnodes中有一段結構沒有被複用,開始和結束位置向中間靠攏,因此沒有被複用的位置是oldChildrenVnodes的最左邊和最右邊之間節點,刪除節點對應的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結構是如何生成的?

如上圖,在isShow1=true,isShow2=true,key="aliarmo"條件下,生成的vnode結構是: oldVnode1,oldVnodeChildren=[oldVnode2,oldVnode3,oldVnode4,oldVnode5]

對應的dom結構為:

Vue底層架構及其應用

改變state為isShow1=false,isShow2=true,key="wmy"後,生成的新vnode結構是

newVnode1,newVnodeChildren=[newVnode3,newVnode4,newVnode5]

最左邊兩個新老vnode對比,也就是oldVnode2newVnode3,不是sameVnode

那最右邊兩個新老vnode對比,也就是oldVnode5newVnode5,是sameVnode,不用移動原來的Element5所在位置,原有dom結構未發生變化,

最左邊兩個新老vnode對比,也就是oldVnode2newVnode3,不是sameVnode

那最右邊兩個新老vnode對比,也就是oldVnode4newVnode4,是sameVnode,不用移動原來的Element4所在位置,原有dom結構未發生變化,

最左邊兩個新老vnode對比,也就是oldVnode2newVnode3,不是sameVnode

那最右邊兩個新老vnode對比,也就是oldVnode3newVnode3,由於key值不同,不是sameVnode

當前最左邊和最右邊對比,oldVnode2newVnode3,不是sameVnode

當前最右邊和最左邊對比,oldVnode5newVnode3,不是sameVnode

在遍歷oldVnodeChildren,尋找與newVnode3sameVnodeoldVnode,沒有找到,則用newVnode3建立一個新的元素Element6,插入到當前oldVnode2所對應元素的最左邊,dom結構發生變化 newVnodeChildren兩頭重合,退出迴圈,刪除剩餘未被複用元素Element2Element3

1.8 core總結

現在我們終於可以理一下,從new Vue()開始,core裡面發生了些什麼

  1. new Vue()或者new自定義元件建構函式(繼承自Vue)
  2. 初始化,props,methods,computed,data,watch,並給state加上Observe,呼叫生命週期created
  3. 開始mount元件,mount之前確保render函式的生成
  4. new Watcher,導致render和patch,注意一個watcher對應一個元件,watcher對變化的響應是重新執行render生成vnode進行patch
  5. render在當前元件上下文(元件例項)執行,生成對應的vnode結構
  6. 如果沒有oldVnode,那patch就是深度遍歷vnode,生成具體的平臺元素,給具體的平臺元素新增屬性和繫結事件,呼叫自定義指令提供的鉤子函式,並append到已存在的元素上,在遍歷的過程中,如果遇到的是自定義元件,則從步驟1開始重複
  7. 如果有oldVnode,那patch就是利用vnode diff演算法在原有的平臺元素上進行修修補補,不到萬不得已不建立新的平臺元素
  8. state發生變化,通知到state所在元件對應的watcher,重新執行render生成vnode進行patch,也就是回到步驟4

2、compiler

Vue的compiler部分負責對template的編譯,生成render和staticRender函式,編譯一次永久使用,所以一般我們在構建的時候就做了這件事情,以提高頁面效能。執行render和staticRender函式可以生成VNode,從而為core提供這一層抽象。

template ==》 AST ==》 遞迴ATS生成render和staticRender ==》VNode

(1)template轉化成AST過程

先讓我們來直觀感受下AST,它描述了下面的template結構

// 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>

 `
複製程式碼

Vue底層架構及其應用

下面描述下template轉為AST的簡要過程:

  1. 如果template是以<開始的字串,則判斷是評論,還是Doctype還是結束標籤,或者是開始標籤,這裡只說處理開始和結束標籤。

(1)如果是開始標籤,則處理類似於下面字串

<div class="Test" :class="classObj" v-show="isShow">
複製程式碼

通過正則可以很容易解析出tag,所有屬性列表,再對屬性列表進行分類,分別解析出v-if,v-for等指令,事件,特殊屬性等,template去除被解析的部分,回到步驟1

(2)如果是結束標籤,則處理類似於下面字串,同樣template去除被解析的部分,回到步驟1

</div>
複製程式碼
  1. 如果不是第一種情況,說明是字串,處理類似於下面的插值字串或者純文字,同樣template去除被解析的部分,回到步驟1
{{username}}:{{password}} 或者 使用者名稱:密碼
複製程式碼
  1. 如果template為空,解析結束

(2)AST生成render和staticRender

主要是遍歷ast(有興趣的同學可以自己體驗下,如:遍歷AST生成還原上述模板,相信會有不一樣的體驗),根據每個節點的屬性拼接渲染函式的字串,如:模板中有v-if="isVisiable",那麼AST中這個節點就會有一個if屬性,這樣,在建立這個節點對應的VNode的時候,就會有

(isVisiable) ? _c('div') : _e()
複製程式碼

在with的作用下,isVisiable 的值決定了VNode是否生成。當然,對於一些指令,在編譯時是處理不了的,會在生成VNode的時候掛載在VNode上,解析VNode時再進行進一步處理,比如v-show,v-on。

下面是上面模板生成的render和staticRender:

// render函式
(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分別用於建立VNode和字串,像username和password是在定義元件時候傳入的state並被掛載在this上。

3、platform

platform模組與具體平臺相關,我們可以在這裡定義平臺相關介面傳入runtime和compile,以實現具體平臺的定製化,因此為其他平臺帶來Vue能力,大部分工作在這裡。

需要傳入runtime的是如何建立具體的平臺元素,平臺元素之間的關係以及如何append,insert,remove平臺元素等,元素生成後需要進行的屬性,事件監聽等。拿web平臺舉例,我們需要傳入document.createElement,document.createTextNode,遍歷vnode的時候生成HTML元素;掛載時需要的insertBefore;state發生變化導致vnode diff時的remove,append等。還有生成HTML元素後,用setAttribute和removeAttribute操作屬性;addEventListener和removeEventListener進行事件監聽;提供一些有利於web平臺使用的自定義元件和指令等等

需要傳入compile的是對某些特殊屬性或者指令在編譯時的處理。如web平臺,需要對class,style,model的特殊處理,以區別於一般的HTML屬性;提供web平臺專用指令,v-html(編譯後其實是繫結元素的innerHTML),v-text(編譯後其實是繫結元素的textContent),v-model,這些指令依賴於具體的平臺元素。

三、應用

說了這麼多,最終目的是為了複用Vue的core和compile,以期在其他的平臺上帶來Vue或者類Vue的開發體驗,前面也說了很多複用成功的例子,如果你要為某一平臺帶來Vue的開發體驗,可以進行參考。大前端概念下,指不定那天,汽車螢幕,智慧手錶等終端介面就可以用Vue來進行開發,爽歪歪。那麼,如何複用呢?當然我只說必選的,你可以定製更多更復雜的功能方便具體平臺使用。

  1. 定義vnode生成具體平臺元素所需要的nodeOps,也就是元素的增刪改查,對於web來說nodeOps是要建立,移動真正的dom物件,如果是其他平臺,可自行定義這些元素的操作方法;

  2. 定義vnode生成具體平臺元素所需要的modules,對於web來說,modules是用來操作dom屬性的方法;

  3. 具體平臺需要定義一個自己的$mount方法給Vue,掛載元件到已存在的元素上;

  4. 還有一些方法,如isReservedTag,是否為保留的標籤名,自定義元件名稱不能與這些相同;mustUseProp,判定元素的某個屬性必須要跟元件state繫結,不能繫結一個常量等等;

四、總結

軟體行業有一句名言,沒有什麼問題是新增一層抽象層不能解決的,如果有那就兩層,Vue的設計正是如此(可能Vue也是借鑑別人的),compile的AST抽象層銜接了模板語法和render函式,經過VNode這個抽象層讓core剝離了具體平臺。

這篇文章的最終目的是為了讓大家瞭解到複用Vue原始碼的core和compile進行二次開發,可以做到為具體平臺帶來Vue或者類Vue的開發體驗。當然,這只是一種思路,說不定哪天,Vue風格開發體驗失寵了,那麼我們也可以把這種思路應用到新開發風格上。


Vue底層架構及其應用

關注【IVWEB社群】公眾號獲取每週最新文章,通往人生之巔!

相關文章