閱讀時間大約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渲染。
我將從如下幾個方面來說明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。
明白這個關係很重要:觀察者包含對變動做出響應的定義,一個元件對應一個觀察者對應元件裡面的所有被觀察者,被觀察者可能被用於其他元件,那麼一個被觀察者會對應多個觀察者,當被觀察者發生變動時,通知到所有觀察者做出更新響應。
元件A的state1發生了變化,那會導致觀察了這個state1的watcher收到變動通知,會導致元件A重新渲染生成新的vnode,在元件A新vnode和老的vnode patch的過程中,會updateChildrenComponent,也就是導致子元件B的props被重新設定一個新值,因為子元件B是有觀察傳入的state1的,因此會通知到相應watcher,導致子元件B的更新
整個watcher體系的建立過程:
- 建立元件例項的時候會對data和props進行observer,
- 對傳入的props進行淺遍歷,重新設定屬性的屬性描述符get和set,如果props的某個屬性值為物件,那麼這個物件在父元件是被深度observe過的,所以props是淺遍歷
- observer會深度遍歷data,對data所包含屬性重新定義,即defineReactive,重新設定屬性描述符的get和set
- 在mountComponent的時候,會new Wacther,當前watcher例項會被pushTarget,設定為目標watcher,然後執行
vm._update(vm._render(), hydrating)
,執行render函式導致屬性的get函式被呼叫,每個屬性會對應一個dep例項,在這個時候,dep例項關聯到元件對應的watcher,實現依賴收集,關聯後popTarget。 - 如果有子元件,會導致子元件的例項化,重新執行上述步驟
state變動響應過程:
- 當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,我只大體列一下:
- 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,標籤為input則type必須相同,還有些其他的條件跟我們不太相關就不列出來了。
整個vnode diff流程
大前提,要看懂這個vnode diff,務必先明白vnode是啥,如何生成的,vnode與elm的關係,詳情請看上面的vnode概念
- 如果兩個vnode是sameVnode,則進行patch vnode
- 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情況
- 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結構為:
改變state為isShow1=false,isShow2=true,key="wmy"
後,生成的新vnode結構是
newVnode1,newVnodeChildren=[newVnode3,newVnode4,newVnode5]
最左邊兩個新老vnode對比,也就是oldVnode2
,newVnode3
,不是sameVnode
,
那最右邊兩個新老vnode對比,也就是oldVnode5
,newVnode5
,是sameVnode
,不用移動原來的Element5所在位置,原有dom結構未發生變化,
最左邊兩個新老vnode對比,也就是oldVnode2
,newVnode3
,不是sameVnode
,
那最右邊兩個新老vnode對比,也就是oldVnode4
,newVnode4
,是sameVnode
,不用移動原來的Element4所在位置,原有dom結構未發生變化,
最左邊兩個新老vnode對比,也就是oldVnode2
,newVnode3
,不是sameVnode
,
那最右邊兩個新老vnode對比,也就是oldVnode3
,newVnode3
,由於key值不同,不是sameVnode
,
當前最左邊和最右邊對比,oldVnode2
,newVnode3
,不是sameVnode
當前最右邊和最左邊對比,oldVnode5
,newVnode3
,不是sameVnode
在遍歷oldVnodeChildren,尋找與newVnode3
為sameVnode
的oldVnode
,沒有找到,則用newVnode3
建立一個新的元素Element6
,插入到當前oldVnode2
所對應元素的最左邊,dom結構發生變化
newVnodeChildren
兩頭重合,退出迴圈,刪除剩餘未被複用元素Element2
,Element3
1.8 core總結
現在我們終於可以理一下,從new Vue()開始,core裡面發生了些什麼
- new Vue()或者new自定義元件建構函式(繼承自Vue)
- 初始化,props,methods,computed,data,watch,並給state加上Observe,呼叫生命週期created
- 開始mount元件,mount之前確保render函式的生成
- new Watcher,導致render和patch,注意一個watcher對應一個元件,watcher對變化的響應是重新執行render生成vnode進行patch
- render在當前元件上下文(元件例項)執行,生成對應的vnode結構
- 如果沒有oldVnode,那patch就是深度遍歷vnode,生成具體的平臺元素,給具體的平臺元素新增屬性和繫結事件,呼叫自定義指令提供的鉤子函式,並append到已存在的元素上,在遍歷的過程中,如果遇到的是自定義元件,則從步驟1開始重複
- 如果有oldVnode,那patch就是利用vnode diff演算法在原有的平臺元素上進行修修補補,不到萬不得已不建立新的平臺元素
- 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>
`
複製程式碼
下面描述下template轉為AST的簡要過程:
- 如果template是以<開始的字串,則判斷是評論,還是Doctype還是結束標籤,或者是開始標籤,這裡只說處理開始和結束標籤。
(1)如果是開始標籤,則處理類似於下面字串
<div class="Test" :class="classObj" v-show="isShow">
複製程式碼
通過正則可以很容易解析出tag,所有屬性列表,再對屬性列表進行分類,分別解析出v-if,v-for等指令,事件,特殊屬性等,template去除被解析的部分,回到步驟1
(2)如果是結束標籤,則處理類似於下面字串,同樣template去除被解析的部分,回到步驟1
</div>
複製程式碼
- 如果不是第一種情況,說明是字串,處理類似於下面的插值字串或者純文字,同樣template去除被解析的部分,回到步驟1
{{username}}:{{password}} 或者 使用者名稱:密碼
複製程式碼
- 如果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來進行開發,爽歪歪。那麼,如何複用呢?當然我只說必選的,你可以定製更多更復雜的功能方便具體平臺使用。
-
定義vnode生成具體平臺元素所需要的nodeOps,也就是元素的增刪改查,對於web來說nodeOps是要建立,移動真正的dom物件,如果是其他平臺,可自行定義這些元素的操作方法;
-
定義vnode生成具體平臺元素所需要的modules,對於web來說,modules是用來操作dom屬性的方法;
-
具體平臺需要定義一個自己的$mount方法給Vue,掛載元件到已存在的元素上;
-
還有一些方法,如isReservedTag,是否為保留的標籤名,自定義元件名稱不能與這些相同;mustUseProp,判定元素的某個屬性必須要跟元件state繫結,不能繫結一個常量等等;
四、總結
軟體行業有一句名言,沒有什麼問題是新增一層抽象層不能解決的,如果有那就兩層,Vue的設計正是如此(可能Vue也是借鑑別人的),compile的AST抽象層銜接了模板語法和render函式,經過VNode這個抽象層讓core剝離了具體平臺。
這篇文章的最終目的是為了讓大家瞭解到複用Vue原始碼的core和compile進行二次開發,可以做到為具體平臺帶來Vue或者類Vue的開發體驗。當然,這只是一種思路,說不定哪天,Vue風格開發體驗失寵了,那麼我們也可以把這種思路應用到新開發風格上。
關注【IVWEB社群】公眾號獲取每週最新文章,通往人生之巔!