关于解析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)。