淺析Vue原始碼(五)—— $mount中template的編譯--optimize

DIVI發表於2018-10-05

上篇文章淺析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提供的思路

相關文章