关于Vue2解析模板优化源码解析

629 阅读1分钟

关于解析html时优化策略。
当vue模板解析生成render函数的时候,会有一个优化的过程optimize,源码中是这样的:

var ast = parse(template.trim(), options);
if (options.optimize !== false) {
   optimize(ast, options);
}
var code = generate(ast, options);
...

这个优化其实是为了render函数生成虚拟DOM,即VNode得时候,给VNode标记为静态节点。静态节点就是当patch(或者diff)算法执行时,也就是新旧VNode比较变化时,不需要比较而直接生成或者拷贝的节点。比如像一个这样的节点:

<div>
      <p>段落</p>
      <div>块级block</div>
</div>

以上就是一个静态节点。 所以优化的主要目的是节省虚拟DOM比较的时间,提高编译的性能。
下面看一下优化的源码:

function optimize(root, options) {
    if (!root) {
        return
    }
    isStaticKey = genStaticKeysCached(options.staticKeys || '');
    isPlatformReservedTag = options.isReservedTag || no;
    // first pass: mark all non-static nodes.
    markStatic$1(root);
    // second pass: mark static roots.
    markStaticRoots(root, false);
}

这里面主要有两个方法:markStatic$1和markStaticRoots。

markStatic$1方法是给所有节点赋值静态属性,其中如果节点的type是3,设置为为静态节点,因为该类型的节点就是纯文本或者注释;如果type是2,不是静态节点,因为该类型的节点会有表达式;如果type是1,且满足很多条件,才可能是静态节点。比如前面提到的html片段,最外层的div就是一个静态节点。
该方法是一个递归,从外层依次向内设置静态节点,所以如果一个节点是元素,还要判断他的后代元素是不是都是静态节点,只要有一个不是,那么这个元素节点就不是静态的。也要给所有的条件节点设置静态属性。

function markStatic$1(node) {
    node.static = isStatic(node);
    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 (!isPlatformReservedTag(node.tag) && node.tag !== 'slot' && node.attrsMap['inline-template'] == null) {
            return
        }
        for (var i = 0, l = node.children.length; i < l; i++) {
            var child = node.children[i];
            markStatic$1(child);
            if (!child.static) {
                node.static = false;
            }
        }
        if (node.ifConditions) {
            for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
                var block = node.ifConditions[i$1].block;
                markStatic$1(block);
                if (!block.static) {
                    node.static = false;
                }
            }
        }
    }
}
function isStatic(node) {
    if (node.type === 2) {
        // expression
        return false
    }
    if (node.type === 3) {
        // text
        return true
    }
    return !!(node.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)))
}

markStaticRoots方法,是为了找到一个静态节点,该节点所有的后代元素都是静态的,这是为了VNode比较的时候,直接复制根节点,而不用再把子节点进行比较或者复制。

function markStaticRoots(node, isInFor) {
    if (node.type === 1) {
        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 (var i = 0, l = node.children.length; i < l; i++) {
                markStaticRoots(node.children[i], isInFor || !!node.for);
            }
        }
        if (node.ifConditions) {
            for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
                markStaticRoots(node.ifConditions[i$1].block, isInFor);
            }
        }
    }
}

下面是我的一个例子

<div>
    <!-- <div v-if="name === '名称'">{{ name }}</div>
    <div v-if="name === '胡玉洋'">{{ name }}</div> -->
    <!-- <button @click="updateName">更改数据</button> -->
    <hy-tabs v-model="id" v-if="id">
      <hy-tab :id="item.id" :title="item.title" v-for="item in list">{{
        item.name
      }}</hy-tab>
    </hy-tabs>
    <div>
      <p>段落</p>
      <div>块级block</div>
    </div>
    <div>
      <p>段落1</p>
      <div>块级block1</div>
    </div>
</div>

下面是生成的render函数:

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "render": () => (/* binding */ render),
/* harmony export */   "staticRenderFns": () => (/* binding */ staticRenderFns)
/* harmony export */ });
var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    [
      _vm.id
        ? _c(
            "hy-tabs",
            {
              model: {
                value: _vm.id,
                callback: function($$v) {
                  _vm.id = $$v
                },
                expression: "id"
              }
            },
            _vm._l(_vm.list, function(item) {
              return _c(
                "hy-tab",
                { attrs: { id: item.id, title: item.title } },
                [_vm._v(_vm._s(item.name))]
              )
            }),
            1
          )
        : _vm._e(),
      _vm._v(" "),
      _vm._m(0),
      _vm._v(" "),
      _vm._m(1)
    ],
    1
  )
}
var staticRenderFns = [
  function() {
    var _vm = this
    var _h = _vm.$createElement
    var _c = _vm._self._c || _h
    return _c("div", [
      _c("p", [_vm._v("段落")]),
      _vm._v(" "),
      _c("div", [_vm._v("块级block")])
    ])
  },
  function() {
    var _vm = this
    var _h = _vm.$createElement
    var _c = _vm._self._c || _h
    return _c("div", [
      _c("p", [_vm._v("段落1")]),
      _vm._v(" "),
      _c("div", [_vm._v("块级block1")])
    ])
  }
]
render._withStripped = true



可以看到有一个vm._m(0)和vm._m(1), 这就是staticRoot的作用,把某些静态节点的最小集合找出来,再渲染。 我的模板里面有两个最小集合分别就是:

<div>
      <p>段落</p>
      <div>块级block</div>
</div>

<div>
      <p>段落1</p>
      <div>块级block1</div>
</div>

分别对应了_m(0)和_m(1)。