筆者公司的前端小組掀起了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;
};
};
複製代碼