VUE源码学习第八篇--编译(optimize)

一、总述

上一章节我们讲到通过解析将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做准备,提升效率。

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/88880620