筆者公司的前端小組掀起了Vue源碼學習小組,先後幾個月的共同窗習,讓小組成員都已經對Vue對大體框架有了個模糊對輪廓。 如今已經進入第二階段:整理。javascript
咱們將小組分爲四個部分,vue對整理也分爲三個大模塊:數據綁定、從template到vnode、vnode轉化爲dom對patch。html
筆者對小組被分到了template到vnode對部分,拿到手後,感受內容比較多,就先將內容根據源碼對佈局分爲兩小塊:parse 和 render過程。前端
再者,古人云:兵馬未動,糧草先行。筆者打算先介紹思想和思路,而後在具體到一些細枝末節。vue
本文準備到就是parse部分思想。java
咱們都知道使用vue到時候,咱們使用定製樣式到幾種方式大體爲:node
咱們須要明白到是不管是哪一種方式,咱們最終的目的都是
生成以vnode爲單位vdom樹express
而生成的vdom樹,最終是用於patch過程當中,生成真實dom節點。瀏覽器
因此咱們的編譯的最終目的是得到:緩存
形式大體以下圖:性能優化
暫時不用管每一個屬性的做用,咱們已經知道咱們的目標是怎樣的了。那麼還須要知道的是 起始點和過程。
正常狀況下,若是咱們這樣初始化的話:
new Vue({ el: '#el', template: ` <div> <div v-for="(item,index) in options" :key="item.id"> {{item.id}} <div>{{item.text}}</div> </div> </div> `, data: { name:"dinglei", options: [ { id: 1, text: 'Hello' }, { id: 2, text: 'World' } ] } }) 複製代碼
那咱們的初始化的狀態則是 template的 一串 String。
固然初始化爲String的寫法仍是滿多的。 好比:
一串String的模版
到 virtue dom tree
的過程。一次性
到位的過程。多是由於尤大的設計的vnode和原生的dom屬性差距過大,直接編譯成vnode很差完成。其次是考慮到性能優化等方面。因此vue的編譯過程實際上是分爲三個過程:
分別對應三個過程:
流程大體以下圖:
接下來咱們將進入本文的主題
首先須要明白astElement包括哪些屬性。在vue源碼 flow文件目錄下的compiler.js能夠找到astElement的模型
declare type ASTElement = { type: 1; tag: string; attrsList: Array<ASTAttr>; attrsMap: { [key: string]: any }; rawAttrsMap: { [key: string]: ASTAttr }; parent: ASTElement | void; children: Array<ASTNode>; start?: number; end?: number; processed?: true; static?: boolean; staticRoot?: boolean; staticInFor?: boolean; staticProcessed?: boolean; hasBindings?: boolean; text?: string; attrs?: Array<ASTAttr>; dynamicAttrs?: Array<ASTAttr>; props?: Array<ASTAttr>; plain?: boolean; pre?: true; ns?: string; component?: string; inlineTemplate?: true; transitionMode?: string | null; slotName?: ?string; slotTarget?: ?string; slotTargetDynamic?: boolean; slotScope?: ?string; scopedSlots?: { [name: string]: ASTElement }; ref?: string; refInFor?: boolean; if?: string; ifProcessed?: boolean; elseif?: string; else?: true; ifConditions?: ASTIfConditions; for?: string; forProcessed?: boolean; key?: string; alias?: string; iterator1?: string; iterator2?: string; staticClass?: string; classBinding?: string; staticStyle?: string; styleBinding?: string; events?: ASTElementHandlers; nativeEvents?: ASTElementHandlers; transition?: string | true; transitionOnAppear?: boolean; model?: { value: string; callback: string; expression: string; }; directives?: Array<ASTDirective>; forbidden?: true; once?: true; onceProcessed?: boolean; wrapData?: (code: string) => string; wrapListeners?: (code: string) => string; // 2.4 ssr optimization ssrOptimizability?: number; // weex specific appendAsTree?: boolean; }; 複製代碼
角色
1.就是識別器(1)
利用正則從前到後識別全部敏感字段。如(標籤、事件、迭代、數據綁定、插槽等等)
2.開始標籤 例如<div>{{test}}</div>
裏的<div>
,識別器(1)
一旦識別到此類標籤,就會使用開始函數start (5)
來對開始標籤作統一處理,而start函數 會建立一個astElement並 存放到stack
當中,當時建立的方式是利用建立函數(3)
建立的,同時也會處理一些非組件屬性(具體如v-model、v-if、v-for)。這裏須要注意的是:
瀏覽器等識別
方式達到目標一致。具體點擊此處3.識別到閉合標籤,則會轉交給end函數
來處理,end函數 會摘取原生屬性如:id 、 class等等、還有ref、slot、component、key等等,並跟上下文(4)
創建起父子關係
。最終造成了樹的結構。
4.一直重複 1~3直到stack
沒有任何標籤。
流程圖大體以下:
其中的細節特別多,若是有興趣請關注咱們的動態,咱們會在下期給出詳細過程講解。 點擊這裏瞭解更多
咱們在上面已經給出了astElement的詳細屬性,其中有兩個屬性叫作staticRoot 和 staticInFor。而optimize 的過程,就是給astElement打上這兩個標記。這裏是爲了讓這類靜態節點,在render過程,可以走緩存的方式,只渲染一次。
好處很明顯,就是靜態節點不須要重複對比和 從新渲染,以此來提升總體性能。
with(this) { _c('div'...xxx...xxx) } 複製代碼
咱們在強調下,此次又是從astElement直接到另外一個String。 上面astElement是個樹狀結構。
而後在這個過程,基本一個astElement就對應一個短函數。 最基本短函數是createElement 也就是_c。 最終的樹狀結構,會以函數的形式表現處理函數以下
_c( 'div', { key:'xxx', ref:'xx', pre:'xxx', domPro:xxx, .... }, [ // chidren _v(_s('ding')), _c('p',{model:'isshow',}, [ ...xxx ]) ] ) 複製代碼
能夠清晰的看到,最終造成的string,依然是一個樹狀形式
,是以function形式展現的樹狀,並且全部屬性都已經抽離成createElement的第二個參數。
1.短函數最終執行短this其實就是Vue實例,或者組件實例。
2.短函數比較重要的三個建立函數分別是_c(createElement)、_v(createTextVNode)、_e(createEmptyVNode),分別生成三種類型的vnode。
一句話歸納下code generate作的事情就是:
生成vnode的前置工做,抽離astElement全部的屬性,造成短函數鏈。
短函數對應大體以下:
export function installRenderHelpers (target: any) { target._o = markOnce // v-once target._n = toNumber target._s = toString target._l = renderList // v-for target._t = renderSlot // slot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic // static target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots // scopeSlot target._g = bindObjectListeners // linsners target._d = bindDynamicKeys target._p = prependModifier } 複製代碼
這個過程是直接執行,就會獲得以vnode爲節點對虛擬dom樹,其細節包括了component的處理,會有單獨一節去介紹。 最後這些虛擬樹會丟給patch函數,最終在不斷對比對過程當中生成目標真實對dom樹。
這裏展現下vnode的格式。
declare interface VNodeData { key?: string | number; slot?: string; ref?: string; is?: string; pre?: boolean; tag?: string; staticClass?: string; class?: any; staticStyle?: { [key: string]: any }; style?: string | Array<Object> | Object; normalizedStyle?: Object; props?: { [key: string]: any }; attrs?: { [key: string]: string }; domProps?: { [key: string]: any }; hook?: { [key: string]: Function }; on?: ?{ [key: string]: Function | Array<Function> }; nativeOn?: { [key: string]: Function | Array<Function> }; transition?: Object; show?: boolean; // marker for v-show inlineTemplate?: { render: Function; staticRenderFns: Array<Function>; }; directives?: Array<VNodeDirective>; keepAlive?: boolean; scopedSlots?: { [key: string]: Function }; model?: { value: any; callback: Function; }; }; 複製代碼