Vue编译器optimize源码分析

2022-07-13,,,,

引言

接上文 parsehtml 函数源码解析 chars、end、comment钩子函数

上一章节我们讲到通过解析将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.
 */
  • 永远不需要变化的dom就是静态的。
  • 重新渲染时,作为常量,无需创建新节点;

optimize 源码之旅

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);
}

可以看到源码并不复杂初始定义了两个变量。

  • isstatickey 获取 genstatickeyscached函数返回值, 获取 makemap (点此查看) 函数返回值引用 。
  • isplatformreservedtag 获取编译器选项 isreservedtag 的引用,检查给定的字符是否是保留的标签。

接下来就是两个重要的方法 markstatic$1 标注静态节点、markstaticroots 标注静态根节点,我们先来看下 markstatic$1的源码。

markstatic$1源码

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;
				}
			}
		}
	}
}

第一步判断节点状态并标注。

node.static = isstatic(node);
在这给元素描述对象(ast) 扩展了static属性,通过isstatic方法调用后返回值,确认哪些节点是静态的,哪些是动态的。

isstatic源码

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)
	))
}

在这判断node.type的值为2,表示为表达式返回false,node.type的值为3,表示为静态文本返回 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)
))

节点类型为表达式,标注为非静态;普通文本为静态。

  • 无动态绑定
  • 没有 v-if 和 v-for 指令
  • 不是内置的标签
  • 是平台保留标签(html和svg标签)
  • 不是 template 标签的直接子元素并且没有包含在 for 循环中
  • 结点包含的属性只能有isstatickey中指定的几个

现在你知道了 node.static=isstatic(node) 什么情况为false, 什么情况为true吧!

回归到markstatic$1

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;
			}
		}
	}
}

来看看它做了什么,通过一个 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
}

当遇到了非平台保留标签 isplatformreservedtag(node.tag), 并且标签节点是 slot,并且节点中有inline-template(内联模板)三者都满足此时会终止函数的执行。

如果不满足条件:

for (var i = 0, l = node.children.length; i &lt; l; i++) {
	var child = node.children[i];
	markstatic$1(child);
	if (!child.static) {
		node.static = false;
	}
}

通过 node.children 找到子节点,递归子节点。

if (!child.static) {
     node.static = false;
}

子节点非静态,该节点也标注非静态 。这块设计的不太合理有更多好的优化方案,在vue3.0中增加了"动静分离的策略" 尤大称之为 block tree 后续在跟大家掰扯。

接下来看下 markstaticroots。

markstaticroots 源码

function markstaticroots(node, isinfor) {
	if (node.type === 1) {
		if (node.static || node.once) {
			node.staticinfor = isinfor;
		}
		//一个节点要成为根节点,那么要满足以下条件:
                //1、静态节点,并且有子节点
                //2、子节点不能仅为一个文本节点
		if (node.static &amp;&amp; node.children.length &amp;&amp; !(
				node.children.length === 1 &amp;&amp;
				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 &lt; l; i++) {
				markstaticroots(node.children[i], isinfor || !!node.for);
			}
		}
		if (node.ifconditions) {
			for (var i$1 = 1, l$1 = node.ifconditions.length; i$1 &lt; l$1; i$1++) {
				markstaticroots(node.ifconditions[i$1].block, isinfor);
			}
		}
	}
}

一个节点要成为静态根节点,需要满足以下条件:

  • 自身为静态节点,并且有子节点
  • 子节点不能仅为一个文本节点

对于第二个条件,主要考虑到标记静态根节点的受益较小。接下来递归循环其子节点,循环标记。

以上就是vue 编译器optimize源码分析的详细内容,更多关于vue 编译器optimize的资料请关注其它相关文章!

《Vue编译器optimize源码分析.doc》

下载本文的Word格式文档,以方便收藏与打印。