天天看點

VUE源碼學習第八篇--編譯(optimize)一、總述二、markStatic三、markStaticRoots五、總結

一、總述

上一章節我們講到通過解析将template轉成AST模型樹,接下來繼續對模型樹優化,進行靜态标注。那麼問題來了,什麼是靜态标注?為什麼要靜态标注。

在源碼的注釋中我們找到了下面這段話:

/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 */
           

1、永遠不需要變化的DOM就是靜态的。

2、重新渲染時,作為常量,無需建立新節點;在patch的過程中可以忽略他們(後面會專門介紹)。

接下來我們開始源碼之旅,這個子產品的代碼相對簡單。

二、markStatic

optimize方法位于src/compiler/optimize.js,

export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  //1、标注靜态節點
  markStatic(root)
  //2、标注靜态根節點
  markStaticRoots(root, false)
}
           

整個過程分為兩部分,第一部分标注靜态節點,第二部分标注靜态根節點。首先看markStatic這個方法。

function markStatic (node: ASTNode) {
  //1、标注節點的狀态
  node.static = isStatic(node)
  //2、對标簽節點進行處理
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    if (
      //非平台保留标簽(html,svg)
      !isPlatformReservedTag(node.tag) &&
      //不是slot标簽
      node.tag !== 'slot' &&
      //不是一個内聯模闆容器
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    //遞歸其子節點,标注狀态
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      //子節點非靜态,則該節點也标注非靜态
      if (!child.static) {
        node.static = false
      }
    }
    //對ifConditions進行循環遞歸
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        const block = node.ifConditions[i].block
        markStatic(block)
        if (!block.static) {
          node.static = false
        }
      }
    }
  }
}
           

1、第一步,判斷節點狀态并标注。

function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { //表達式,标注非靜态
    return false
  }
  if (node.type === 3) { // 普通文本,标注靜态
    return true
  }
  return !!(node.pre || (//v-pre 指令||
    !node.hasBindings && // (無動态綁定&&
    !node.if && !node.for && // 沒有 v-if 和 v-for &&
    !isBuiltInTag(node.tag) && // 不是内置的标簽,内置的标簽有slot和component &&
    isPlatformReservedTag(node.tag) && //是平台保留标簽&&
    !isDirectChildOfTemplateFor(node) &&//不是 template 标簽的直接子元素并且沒有包含在 for 循環中
    Object.keys(node).every(isStaticKey)//結點包含的屬性隻能有isStaticKey中指定的幾個)                                   //以上條件滿足則為靜态标簽
  ))
}
           

節點類型為表達式,标注為非靜态;普通文本為靜态。

v-pre指令(無需編譯)标注為靜态,或者滿足以下條件,也标注為靜态。

(1)無動态綁定

(2)沒有 v-if 和 v-for 

(3) 不是内置的标簽,内置的标簽有slot和component 

(4)是平台保留标簽(html和svg标簽)

(5)不是 template 标簽的直接子元素并且沒有包含在 for 循環中

(6)結點包含的屬性隻能有isStaticKey中指定的幾個.

2、第二步,對節點類型為标簽的進行處理。

    首先對slot内容不做遞歸标注,直接傳回;其他的則循環其子節點,進行遞歸标注,如果子節點為非靜态,那麼該節點也要标注非靜态。是以整個标注過程是自下而上,先标注子節點,然後再是父節點,一層一層往上回溯。

對ifConditions的循環也是類似過程。

三、markStaticRoots

繼續看markStaticRoots方法

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
    }
    //一個節點要成為根節點,那麼要滿足以下條件:
    //1、靜态節點,并且有子節點,
    //2、子節點不能僅為一個文本節點
    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)
      }
    }
  }
}
           

一個節點要成為靜态根節點,需要滿足以下條件:

1、自身為靜态節點,并且有子節點,

2、子節點不能僅為一個文本節點

對于第二個條件,主要考慮到标記靜态根節點的受益較小。

接下來遞歸循環其子節點,循環标記。

以前一章節的template為例,标記完成後的AST模型如下:

{
    "type": 1,
    "tag": "div",
    "attrsList": [
        {
            "name": "id",
            "value": "app"
        }
    ],
    "attrsMap": {
        "id": "app"
    },
    "children": [
        {
            "type": 1,
            "tag": "ul",
            "attrsList": [],
            "attrsMap": {},
            "parent": {
                "$ref": "$"
            },
            "children": [
                {
                    "type": 1,
                    "tag": "li",
                    "attrsList": [],
                    "attrsMap": {
                        "v-for": "item in items"
                    },
                    "parent": {
                        "$ref": "$[\"children\"][0]"
                    },
                    "children": [
                        {
                            "type": 2,
                            "expression": "\"\\n      itemid:\"+_s(item.id)+\"\\n    \"",
                            "tokens": [
                                "\n      itemid:",
                                {
                                    "@binding": "item.id"
                                },
                                "\n    "
                            ],
                            "text": "\n      itemid:{{item.id}}\n    ",
                            "static": false
                        }
                    ],
                    "for": "items",
                    "alias": "item",
                    "plain": true,
                    "static": false,
                    "staticRoot": false,
                    "forProcessed": true
                }
            ],
            "plain": true,
            "static": false,
            "staticRoot": false
        }
    ],
    "plain": false,
    "attrs": [
        {
            "name": "id",
            "value": "\"app\""
        }
    ],
    "static": false,
    "staticRoot": false
}
           

可以看到每個節點增加了static,staticRoot兩個屬性,由于葉節點包含了表達式,是以所有的父節點都标記為false。

五、總結

optimize整個過程邏輯較簡單,代碼也不多,給節點标記上static後,為後續的render和patch做準備,提升效率。

繼續閱讀