镇江网站建设zjmfkj,便宜的海外服务器,网页的制作步骤是什么,四川建设网是什么单位1. 前言
在前几篇文章中#xff0c;我们介绍了模板编译流程三大阶段中的第一阶段模板解析阶段#xff0c;在这一阶段主要做的工作是用解析器将用户所写的模板字符串解析成AST抽象语法树#xff0c;理论上来讲#xff0c;有了AST就可直接进入第三阶段生成render函数了。其实…1. 前言
在前几篇文章中我们介绍了模板编译流程三大阶段中的第一阶段模板解析阶段在这一阶段主要做的工作是用解析器将用户所写的模板字符串解析成AST抽象语法树理论上来讲有了AST就可直接进入第三阶段生成render函数了。其实不然Vue还是很看重性能的只要有一点可以优化的地方就要将其进行优化。在之前介绍虚拟DOM的时候我们说过有一种节点一旦首次渲染上了之后不管状态再怎么变化它都不会变了这种节点叫做静态节点如下
ulli我是文本信息/lili我是文本信息/lili我是文本信息/lili我是文本信息/lili我是文本信息/li
/ul在上面代码中ul标签下面有5个li标签每个li标签里的内容都是不含任何变量的纯文本也就是说这种标签一旦第一次被渲染成DOM节点以后之后不管状态再怎么变化它都不会变了我们把像li的这种节点称之为静态节点。而这5个li节点的父节点是ul节点也就是说ul节点的所有子节点都是静态节点那么我们把像ul的这种节点称之为静态根节点。
OK有了静态节点和静态根节点这两个概念之后我们再仔细思考模板编译的最终目的是用模板生成一个render函数而用render函数就可以生成与模板对应的VNode之后再进行patch算法最后完成视图渲染。这中间的patch算法又是用来对比新旧VNode之间存在的差异。在上面我们还说了静态节点不管状态怎么变化它是不会变的基于此那我们就可以在patch过程中不用去对比这些静态节点了这样不就又可以提高一些性能了吗
所以我们在模板编译的时候就先找出模板中所有的静态节点和静态根节点然后给它们打上标记用于告诉后面patch过程打了标记的这些节点是不需要对比的你只要把它们克隆一份去用就好啦。这就是优化阶段存在的意义。
上面也说了优化阶段其实就干了两件事
在AST中找出所有静态节点并打上标记在AST中找出所有静态根节点并打上标记
优化阶段的源码位于src/compiler/optimizer.js中如下
export function optimize (root: ?ASTElement, options: CompilerOptions) {if (!root) returnisStaticKey genStaticKeysCached(options.staticKeys || )isPlatformReservedTag options.isReservedTag || no// 标记静态节点markStatic(root)// 标记静态根节点markStaticRoots(root, false)
}接下来我们就对所干的这两件事逐个分析。
2. 标记静态节点
从AST中找出所有静态节点并标记其实不难我们只需从根节点开始先标记根节点是否为静态节点然后看根节点如果是元素节点那么就去向下递归它的子节点子节点如果还有子节点那就继续向下递归直到标记完所有节点。代码如下
function markStatic (node: ASTNode) {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-reloadingif (!isPlatformReservedTag(node.tag) 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}}if (node.ifConditions) {for (let i 1, l node.ifConditions.length; i l; i) {const block node.ifConditions[i].blockmarkStatic(block)if (!block.static) {node.static false}}}}
}在上面代码中首先调用isStatic函数标记节点是否为静态节点该函数若返回true表示该节点是静态节点若返回false表示该节点不是静态节点函数实现如下
function isStatic (node: ASTNode): boolean {if (node.type 2) { // 包含变量的动态文本节点return false}if (node.type 3) { // 不包含变量的纯文本节点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-inisPlatformReservedTag(node.tag) // not a component!isDirectChildOfTemplateFor(node) Object.keys(node).every(isStaticKey)))
}该函数的实现过程其实也说明了如何判断一个节点是否为静态节点。还记得在HTML解析器在调用钩子函数创建AST节点时会根据节点类型的不同为节点加上不同的type属性用type属性来标记AST节点的节点类型其对应关系如下
type取值对应的AST节点类型1元素节点2包含变量的动态文本节点3不包含变量的纯文本节点
所以在判断一个节点是否为静态节点时首先会根据type值判断节点类型如果type值为2那么该节点是包含变量的动态文本节点它就肯定不是静态节点返回false
if (node.type 2) { // 包含变量的动态文本节点return false
}如果type值为2那么该节点是不包含变量的纯文本节点它就肯定是静态节点返回true
if (node.type 3) { // 不包含变量的纯文本节点return true
}如果type值为1,说明该节点是元素节点那就需要进一步判断。
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-inisPlatformReservedTag(node.tag) // not a component!isDirectChildOfTemplateFor(node) Object.keys(node).every(isStaticKey)
)如果元素节点是静态节点那就必须满足以下几点要求
如果节点使用了v-pre指令那就断定它是静态节点如果节点没有使用v-pre指令那它要成为静态节点必须满足 不能使用动态绑定语法即标签上不能有v-、、:开头的属性不能使用v-if、v-else、v-for指令不能是内置组件即标签名不能是slot和component标签名必须是平台保留标签即不能是组件当前节点的父节点不能是带有 v-for 的 template 标签节点的所有属性的 key 都必须是静态节点才有的 key注静态节点的key是有限的它只能是type,tag,attrsList,attrsMap,plain,parent,children,attrs之一
标记完当前节点是否为静态节点之后如果该节点是元素节点那么还要继续去递归判断它的子节点如下
for (let i 0, l node.children.length; i l; i) {const child node.children[i]markStatic(child)if (!child.static) {node.static false}
}注意在上面代码中新增了一个判断
if (!child.static) {node.static false
}这个判断的意思是如果当前节点的子节点有一个不是静态节点那就把当前节点也标记为非静态节点。为什么要这么做呢这是因为我们在判断的时候是从上往下判断的也就是说先判断当前节点再判断当前节点的子节点如果当前节点在一开始被标记为了静态节点但是通过判断子节点的时候发现有一个子节点却不是静态节点这就有问题了我们之前说过一旦标记为静态节点就说明这个节点首次渲染之后不会再发生任何变化但是它的一个子节点却又是可以变化的就出现了自相矛盾所以我们需要当发现它的子节点中有一个不是静态节点的时候就得把当前节点重新设置为非静态节点。
循环node.children后还不算把所有子节点都遍历完因为如果当前节点的子节点中有标签带有v-if、v-else-if、v-else等指令时这些子节点在每次渲染时都只渲染一个所以其余没有被渲染的肯定不在node.children中而是存在于node.ifConditions所以我们还要把node.ifConditions循环一遍如下
if (node.ifConditions) {for (let i 1, l node.ifConditions.length; i l; i) {const block node.ifConditions[i].blockmarkStatic(block)if (!block.static) {node.static false}}
}同理如果当前节点的node.ifConditions中有一个子节点不是静态节点也要将当前节点设置为非静态节点。
以上就是标记静态节点的全部逻辑。
3. 标记静态根节点
寻找静态根节点根寻找静态节点的逻辑类似都是从AST根节点递归向下遍历寻找其代码如下
function markStaticRoots (node: ASTNode, isInFor: boolean) {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 its 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 truereturn} 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)}}}
}上面代码中首先markStaticRoots 第二个参数是 isInFor对于已经是 static 的节点或者是 v-once 指令的节点node.staticInFor isInFor如下
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 its 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 truereturn
} else {node.staticRoot false
}从代码和注释中我们可以看到一个节点要想成为静态根节点它必须满足以下要求
节点本身必须是静态节点必须拥有子节点 children子节点不能只是只有一个文本节点
否则的话对它的优化成本将大于优化后带来的收益。
如果当前节点不是静态根节点那就继续递归遍历它的子节点node.children和node.ifConditions如下
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)}
}这里的原理跟寻找静态节点相同此处就不再重复。
4. 总结
本篇文章介绍了模板编译过程三大阶段的第二阶段——优化阶段。
首先介绍了为什么要有优化阶段是为了提高虚拟DOM中patch过程的性能。在优化阶段将所有静态节点都打上标记这样在patch过程中就可以跳过对比这些节点。
接着介绍了优化阶段主要干了两件事情分别是从构建出的AST中找出并标记所有静态节点和所有静态根节点。
最后分别通过逐行分析源码的方式分析了这两件事具体的内部工作原理。