上篇文章淺析Vue原始碼(四)—— $mount中template的編譯--parse,我們介紹了compile 的 parse部分,至此我們完成了對一個html字串模板解析成一個AST語法樹的過程。下一步就是我們需要通過optimize方法,將AST節點進行靜態節點標記。為後面 patch 過程中對比新舊 VNode 樹形結構做優化。被標記為 static 的節點在後面的 diff 演算法中會被直接忽略,不做詳細的比較。
src/compiler/optimizer.js
複製程式碼
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
// staticKeys 是那些認為不會被更改靜態的ast的屬性
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
// 第一步 標記 AST 所有靜態節點
markStatic(root)
// second pass: mark static roots.
// 第二步 標記 AST 所有父節點(即子樹根節點)
markStaticRoots(root, false)
}
複製程式碼
首先標記所有靜態節點:
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // 表示式
return false
}
if (node.type === 3) { // 文字節點
return true
}
// 處理特殊標記
return !!(node.pre || ( // v-pre標記的
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
複製程式碼
ASTNode 的 type 欄位用於標識節點的型別,可檢視上一篇的 AST 節點定義:
type 為 1 表示元素,
type 為 2 表示插值表示式,
type 為 3 表示普通文字。
可以看到,在標記 ASTElement 時會依次檢查所有子元素節點的靜態標記,從而得出該元素是否為 static。上面 markStatic 函式使用的是樹形資料結構的深度優先遍歷演算法,使用遞迴實現。 接下來繼續標記靜態樹:
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
// 用以標記在v-for內的靜態節點。這個屬性用以告訴renderStatic(_m)對這個節點生成新的key,避免patch error
if (node.static || node.once) {
node.staticInFor = isInFor
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
// 一個節點如果想要成為靜態根,它的子節點不能單純只是靜態文字。否則,把它單獨提取出來還不如重渲染時總是更新它效能高。
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
複製程式碼
markStaticRoots 函式裡並沒有什麼特別的地方,僅僅是對靜態節點又做了一層篩選。
總結
optimizer旨在為語法樹的節點標上static和staticRoot屬性。 遍歷第一輪,標記static屬性:
判斷node是否為static(有諸多條件) 標記node的children是否為static,若存在non static子節點,父節點更改為static = false 遍歷第二輪,標記staticRoot
標記static或節點為staticRoot,這個節點type === 1(一般是含有tag屬性的節點) 具有v-once指令的節點同樣被標記staticRoot 為了避免過度優化,只有static text為子節點的節點不被標記為staticRoot 標記節點children的staticRoot
要是喜歡的話給我一個star,github
感謝muwoo提供的思路