模板转为抽象语法树(AST)
作为云服务器推荐网的小编,我们之前已经讲过将模板转为抽象语法树(AST),现在我们来接着讲述如何对模型树进行优化,实现静态标注。

优化的目标和实现方法
在源码的注释中我们找到了如下描述:
目标:遍历生成的模板抽象语法树并检测纯静态的子树,即永远不需要变化的DOM部分。一旦检测到这些子树,我们可以:
- 将其提升为常量,这样我们就不需要在每次重渲染时创建新的节点;
- 完全跳过它们的渲染过程。
根据注释,我们可以知道要实现静态标注,需要检测出永远不需要变化的DOM。在重新渲染时,由于已被标记为常量,无需再创建新节点。具体实现方法如下:
- 标记静态节点和静态根节点。
- 将静态节点提升为常量,防止每次重新渲染时创建新节点。
源码分析
现在我们开始分析源码。
function optimize(root, options) {
if (!root) return;
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// 第一次标记:标记所有非静态节点。
markStatic(root);
// 第二次标记:标记静态根节点。
markStaticRoots(root, false);
}
代码看起来很简单,定义了两个变量isStaticKey和isPlatformReservedTag。其中isStaticKey获取genStaticKeysCached函数返回的值,这里的genStaticKeysCached在编译阶段生成了一个函数返回值,并通过makeMap(点此查看)函数返回引用。isPlatformReservedTag获取编译器选项中的isReservedTag,然后检查给定的字符是否是保留的标签。
标记静态节点
接下来我们来看看markStatic方法的实现。
function markStatic(node) {
node.static = isStatic(node);
// 如果节点为元素节点,递归遍历子节点。
if (node.type === 1) {
// 如果元素为非保留标签、不是 、不存在内联模板,则退出循环,标记非静态。
if (!isPlatformReservedTag(node.tag) && node.tag !== 'slot' && !node.attrsMap['inline-template']) {
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;
}
}
// 针对具有v-if指令的节点进行遍历,若存在非静态节点,则该元素标记为非静态节点。
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;
}
}
}
}
}
首先,对节点做出判断并标注node.static属性值。这里的isStatic方法会判断节点是否是静态,如果是则返回true,否则返回false。然后,对所有元素节点递归遍历它的子节点,如果存在非静态子节点,则该元素标记为非静态节点。
接着,如果该元素有v-if指令,也会进行类似的遍历,并将其标记为非静态。
标记静态根节点
接下来我们来看看markStaticRoots方法的实现。
function markStaticRoots(node, isInFor) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor;
}
// 如果该节点为静态节点并有子节点,则它是一个静态根节点。
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));
}
}
// 如果该节点使用v-if指令,则递归遍历。
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor);
}
}
}
}
如果一个节点符合以下两个条件,它就是静态根节点:
- 自身是静态节点
- 自身有子节点,或者其子节点不是文本节点
如果符合,将标记该节点的staticRoot属性值为true。如果一个节点被标记为静态根节点,则其子节点无论是否是静态都被视为静态节点。
接下来,如果该节点的子节点是一个列表,则递归遍历该列表,如果使用了v-if指令,则递归遍历v-if的block。
总结
以上是如何进行静态标注的源码分析,标记静态节点和静态根节点有助于缓存页面并提高访问速度。希望这篇文章可以帮助您更好地理解Vue的工作原理,以及如何优化Vue的性能。
原创文章,作者:小编小本本,如若转载,请注明出处:https://www.benjiyun.com/yunzhujiyunwei/vps-yunwei/7394.html
