Vue3静态提升是什么(vue3,开发技术)

时间:2024-04-28 11:56:41 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

希望大家仔细阅读,能够学有所成!

什么是静态提升

静态提升是Vue3编译优化中的其中一个优化点。所谓的静态提升,就是指在编译器编译的过程中,将一些静态的节点或属性提升到渲染函数之外。下面,我们通过一个例子来深入理解什么是静态提升。

假设我们有如下模板:

<div>
<p>statictext</p>
<p>{{title}}</p>
</div>

在没有静态提升的情况下,它对应的渲染函数是:

functionrender(){
return(openClock(),createClock('div',null,[
createVNode('p',null,'statictext'),
createVNode('p',null,ctx.title,1/TEXT/)
]))
}

从上面的代码中可以看到,在这段虚拟DOM的描述中存在两个 p 标签,一个是纯静态的,而另一个拥有动态文本。当响应式数据 title 的值发生变化时,整个渲染函数会重新执行,并产生新的虚拟DOM树。在这个过程中存在性能开销的问题。原因是纯静态的虚拟节点在更新时也会被重新创建一次,其实这是没有必要的。因此我们需要使用静态提升来解决这个问题。

所谓的 “静态提升”,就是将一些静态的节点或属性提升到渲染函数之外。如下面的代码所示:

//把静态节点提升到渲染函数之外
consthoist1=createVNode('p',null,'text')
functionrender(){
return(openBlock(),createBlock('div',null,[
hoist1,//静态节点引用
createVNode('p',null,ctx.title,1/TEXT/)
]))
}

可以看到,经过静态提升后,在渲染函数内只会持有对静态节点的引用。当响应式数据发生变化,并使得渲染函数重新执行时,并不会重新创建静态的虚拟节点,从而避免了额外的性能开销。

transform 转换器

在模板编译器将模板编译为渲染函数的过程中,transform 函数扮演着十分重要的角色。它用来将模板AST转换为 JavaScript AST。下面,我们来看看 transform 函数做了什么。

//packages/compiler-core/src/transform.ts
//将模板AST转换为JavaScriptAST
exportfunctiontransform(root:RootNode,options:TransformOptions){
//创建转换上下文
constcontext=createTransformContext(root,options)
//遍历所有节点,执行转换
traverseNode(root,context)
//如果编译选项中打开了hoistStatic选项,则进行静态提升
if(options.hoistStatic){
hoistStatic(root,context)
}
//创建Block,收集所有动态子节点
if(!options.ssr){
createRootCodegen(root,context)
}
//finalizemetainformation
//确定最终的元信息
root.helpers=[...context.helpers.keys()]
root.components=[...context.components]
root.directives=[...context.directives]
root.imports=context.imports
root.hoists=context.hoists
root.temps=context.temps
root.cached=context.cached
if(COMPAT){
root.filters=[...context.filters!]
}
}

从上面的源码中可以看到,transform 函数的实现并不复杂。

  • 首先,调用 createTransformContext 函数创建了一个转换上下文对象,该对象存储着转换过程中的一些上下文数据。例如当前正在转换的节点 currentNode、当前转换节点的父节点parent、用于替换当前正在转换的节点的 replaceNode 函数、用于移除当前访问的节点的 removeNode 函数等。

  • 接着调用 traverseNode 函数,递归遍历模板AST,将模板AST 转换为 JavaScript AST。

  • 然后判断编译选项options中是否开启了 hoistStatic,如果是,则进行静态提升。

  • 接下来则创建 Block,收集模板中的动态子节点。

  • 最后做的事情则是确定最终的元信息。

由于本文主要是介绍静态提升,因此我们围绕静态提升的代码继续往下探索,其余部分代码将在其它文章中介绍。

hoistStatic 静态提升

hoistStatic 函数的源码如下所示:

//packages/compiler-core/src/transforms/hoistStatic.ts
exportfunctionhoistStatic(root:RootNode,context:TransformContext){
walk(
root,
context,
//Rootnodeisunfortunatelynon-hoistableduetopotentialparent
//fallthroughattributes.
//根节点作为Block角色是不能被提升的
isSingleElementRoot(root,root.children[0])
)
}

可以看到,hoistStatic 函数接收两个参数,第一个参数是根节点,第二个参数是转换上下文。在该函数中,仅仅只是调用了 walk 函数来实现静态提升。

并且在 walk 函数中调用 isSingleElementRoot 函数来告知 walk 函数根节点是不能提升的,原因是根节点作为 Block 角色是不可被提升的。

我们接下来继续探究 walk 函数的源码。

walk 函数

静态提升的真正实现逻辑在 walk 函数内,其源码如下所示:

functionwalk(
node:ParentNode,
context:TransformContext,//转换上下文对象
doNotHoistNode:boolean=false//节点是否可以被提升
){
const{children}=node
//子节点的数量
constoriginalCount=children.length
//可提升节点的数量
lethoistedCount=0
for(leti=0;i<children.length;i++){
constchild=children[i]
//onlyplainelements&textcallsareeligibleforhoisting.
//只有普通元素和文本才能被提升
if(
child.type===NodeTypes.ELEMENT&&
child.tagType===ElementTypes.ELEMENT
){
//如果节点不能被提升,则将constantType赋值为NOT_CONSTANT不可被提升的标记
//否则调用getConstantType获取子节点的静态类型:ConstantTypes定义了子节点的静态类型
constconstantType=doNotHoistNode
?ConstantTypes.NOT_CONSTANT
:getConstantType(child,context)
//如果获取到的constantType枚举值大于NOT_CONSTANT
if(constantType>ConstantTypes.NOT_CONSTANT){
//如果节点可以被提升
if(constantType>=ConstantTypes.CAN_HOIST){
//则将子节点的codegenNode属性的patchFlag标记为HOISTED,即可提升
;(child.codegenNodeasVNodeCall).patchFlag=
PatchFlags.HOISTED+(DEV?/*HOISTED*/:) //提升节点,将节点存储到转换上下文context的hoist数组中 child.codegenNode=context.hoist(child.codegenNode!) //提升节点数量自增1 hoistedCount++ continue } }else{ //nodemaycontaindynamicchildren,butitspropsmaybeeligiblefor //hoisting. //包含动态绑定的节点本身不会被提升,该动态节点上可能存在纯静态的属性,静态的属性可以被提升 constcodegenNode=child.codegenNode! if(codegenNode.type===NodeTypes.VNODE_CALL){ //获取patchFlag补丁标志 constflag=getPatchFlag(codegenNode) //如果不存在patchFlag补丁标志或者patchFlag是文本类型 //并且该节点的props是可以被提升的 if( (!flag|| flag===PatchFlags.NEED_PATCH|| flag===PatchFlags.TEXT)&amp;&amp; getGeneratedPropsConstantType(child,context)&gt;= ConstantTypes.CAN_HOIST ){ //获取节点的props,并在转换上下文对象中执行提升操作, //将被提升的props添加到转换上下文context的hoist数组中 constprops=getNodeProps(child) if(props){ codegenNode.props=context.hoist(props) } } //将节点的动态props添加到转换上下文对象中 if(codegenNode.dynamicProps){ codegenNode.dynamicProps=context.hoist(codegenNode.dynamicProps) } } } }elseif( //如果是节点类型是TEXT_CALL,并且节点可以被提升 child.type===NodeTypes.TEXT_CALL&amp;&amp; getConstantType(child.content,context)&gt;=ConstantTypes.CAN_HOIST ){ //提升节点 child.codegenNode=context.hoist(child.codegenNode) hoistedCount++ } //walkfurther if(child.type===NodeTypes.ELEMENT){ //如果子节点的tagType是组件,则继续遍历子节点 //以判断插槽中的情况 constisComponent=child.tagType===ElementTypes.COMPONENT if(isComponent){ context.scopes.vSlot++ } //执行walk函数,继续判断插槽中的节点及节点属性是否可以被提升 walk(child,context) if(isComponent){ context.scopes.vSlot-- } }elseif(child.type===NodeTypes.FOR){ //Donothoistv-forsinglechildbecauseithastobeablock //带有v-for指令的节点是一个Block //如果v-for的节点中只有一个子节点,则不能被提升 walk(child,context,child.children.length===1) }elseif(child.type===NodeTypes.IF){ //带有v-if指令的节点是一个Block for(leti=0;i&lt;child.branches.length;i++){ //Donothoistv-ifsinglechildbecauseithastobeablock //如果只有一个分支条件,则不进行提升 walk( child.branches[i], context, child.branches[i].children.length===1 ) } } } //将被提升的节点序列化,即转换成字符串 if(hoistedCount&amp;&amp;context.transformHoist){ context.transformHoist(children,context,node) } //allchildrenwerehoisted-theentirechildrenarrayishoistable. if( hoistedCount&amp;&amp; hoistedCount===originalCount&amp;&amp; node.type===NodeTypes.ELEMENT&amp;&amp; node.tagType===ElementTypes.ELEMENT&amp;&amp; node.codegenNode&amp;&amp; node.codegenNode.type===NodeTypes.VNODE_CALL&amp;&amp; isArray(node.codegenNode.children) ){ node.codegenNode.children=context.hoist( createArrayExpression(node.codegenNode.children) ) } }</pre><p>可以看到,walk 函数的代码比较常,下面,我们将分步对其进行解析。</p><p>1、首先,我们来看 walk 函数的函数签名,代码如下:</p><pre class="brush:js;">functionwalk( node:ParentNode, context:TransformContext,//转换上下文对象 doNotHoistNode:boolean=false//节点是否可以被提升 )</pre><p>从函数签名中可以知道,walk 函数接收三个参数,第一个参数是一个 node 节点,第二个参数是转换器的转换上下文对象 context,第三个参数 doNotHoistNode 是一个布尔值,用来判断传入节点的子节点是否可以被提升。</p><p>2、初始化两个变量,originalCount:子节点的数量;hoistedCount:可提升节点的数量,该变量将会用于判断被提升的节点是否可序列化。</p><pre class="brush:js;">const{children}=node //子节点的数量 constoriginalCount=children.length //可提升节点的数量 lethoistedCount=0</pre><p>3、我们来看 for 循环语句里的第一个 if 语句分支,这里处理的是普通元素和静态文本被提升的情况。</p><pre class="brush:js;">//onlyplainelements&amp;textcallsareeligibleforhoisting. //只有普通元素和文本才能被提升 if( child.type===NodeTypes.ELEMENT&amp;&amp; child.tagType===ElementTypes.ELEMENT ){ //如果节点不能被提升,则将constantType赋值为NOT_CONSTANT不可被提升的标记 //否则调用getConstantType获取子节点的静态类型:ConstantTypes定义了子节点的静态类型 constconstantType=doNotHoistNode ?ConstantTypes.NOT_CONSTANT :getConstantType(child,context) //如果获取到的constantType枚举值大于NOT_CONSTANT if(constantType&gt;ConstantTypes.NOT_CONSTANT){ //如果节点可以被提升 if(constantType&gt;=ConstantTypes.CAN_HOIST){ //则将子节点的codegenNode属性的patchFlag标记为HOISTED,即可提升 ;(child.codegenNodeasVNodeCall).patchFlag= PatchFlags.HOISTED+(__DEV__?`/*HOISTED*/`:)
//提升节点,将节点存储到转换上下文context的hoist数组中
child.codegenNode=context.hoist(child.codegenNode!)
//提升节点数量自增1
hoistedCount++
continue
}
}else{
//nodemaycontaindynamicchildren,butitspropsmaybeeligiblefor
//hoisting.
//包含动态绑定的节点本身不会被提升,该动态节点上可能存在纯静态的属性,静态的属性可以被提升
constcodegenNode=child.codegenNode!
if(codegenNode.type===NodeTypes.VNODE_CALL){
//获取patchFlag补丁标志
constflag=getPatchFlag(codegenNode)
//如果不存在patchFlag补丁标志或者patchFlag是文本类型
//并且该节点的props是可以被提升的
if(
(!flag||
flag===PatchFlags.NEED_PATCH||
flag===PatchFlags.TEXT)&&
getGeneratedPropsConstantType(child,context)>=
ConstantTypes.CAN_HOIST
){
//获取节点的props,并在转换上下文对象中执行提升操作,
//将被提升的props添加到转换上下文context的hoist数组中
constprops=getNodeProps(child)
if(props){
codegenNode.props=context.hoist(props)
}
}
//将节点的动态props添加到转换上下文对象中
if(codegenNode.dynamicProps){
codegenNode.dynamicProps=context.hoist(codegenNode.dynamicProps)
}
}
}

首先通过外部传入的 doNotHoistNode 参数来获取子节点的静态类型。如果 doNotHoistNode 为 true,则将 constantType 的值设置为 ConstantType 枚举值中的 NOT_CONSTANT,即不可被提升。否则通过 getConstantType 函数获取子节点的静态类型。如下面的代码所示:

//如果节点不能被提升,则将constantType赋值为NOT_CONSTANT不可被提升的标记
//否则调用getConstantType获取子节点的静态类型:ConstantTypes定义了子节点的静态类型
constconstantType=doNotHoistNode
?ConstantTypes.NOT_CONSTANT
:getConstantType(child,context)

接下来通过判断 constantType 的枚举值来处理是需要提升静态节点还是提升动态节点的静态属性。

  • 如果获取到的 constantType 枚举值大于 NOT_CONSTANT,说明该节点可能被提升或序列化为字符串。

  • 如果该节点可以被提升,则将节点 codegenNode 属性的 patchFlag 标记为 PatchFlags.HOISTED ,即可提升。

然后执行转换器上下文中的 hoist 方法,将该节点存储到转换上下文context 的 hoist 数组中,该数组中存储的是可被提升的节点。如下面的代码所示:

//如果获取到的constantType枚举值大于NOT_CONSTANT
if(constantType>ConstantTypes.NOT_CONSTANT){
//如果节点可以被提升
if(constantType>=ConstantTypes.CAN_HOIST){
//则将子节点的codegenNode属性的patchFlag标记为HOISTED,即可提升
;(child.codegenNodeasVNodeCall).patchFlag=
PatchFlags.HOISTED+(DEV?/*HOISTED*/:``)
//提升节点,将节点存储到转换上下文context的hoist数组中
child.codegenNode=context.hoist(child.codegenNode!)
//提升节点数量自增1
hoistedCount++
continue
}
}

如果获取到的 constantType 枚举值不大于 NOT_CONSTANT,说明该节点包含动态绑定,包含动态绑定的节点上如果存在纯静态的 props,那么这些静态的 props 是可以被提升的

从下面的代码中我们可以看到,在提升静态的 props 时,同样是执行转换器上下文中的 hoist 方法,将静态的props存储到转换上下文context 的 hoist 数组中。

如下面的代码所示:

else{
//nodemaycontaindynamicchildren,butitspropsmaybeeligiblefor
//hoisting.
//包含动态绑定的节点本身不会被提升,该动态节点上可能存在纯静态的属性,静态的属性可以被提升
constcodegenNode=child.codegenNode!
if(codegenNode.type===NodeTypes.VNODE_CALL){
//获取patchFlag补丁标志
constflag=getPatchFlag(codegenNode)
//如果不存在patchFlag补丁标志或者patchFlag是文本类型
//并且该节点的props是可以被提升的
if(
(!flag||
flag===PatchFlags.NEED_PATCH||
flag===PatchFlags.TEXT)&&
getGeneratedPropsConstantType(child,context)>=
ConstantTypes.CAN_HOIST
){
//获取节点的props,并在转换上下文对象中执行提升操作,
//将被提升的props添加到转换上下文context的hoist数组中
constprops=getNodeProps(child)
if(props){
codegenNode.props=context.hoist(props)
}
}
//将节点的动态props添加到转换上下文对象中
if(codegenNode.dynamicProps){
codegenNode.dynamicProps=context.hoist(codegenNode.dynamicProps)
}
}
}

4、如果节点类型时是 TEXT_CALL 类型并且该节点可以被提升,则同样执行转换器上下文中的 hoist 方法,将节点存储到转换上下文context 的 hoist 数组中,如下面的代码所示:

elseif(
//如果是节点类型是TEXT_CALL,并且节点可以被提升
child.type===NodeTypes.TEXT_CALL&&
getConstantType(child.content,context)>=ConstantTypes.CAN_HOIST
){
//提升节点
child.codegenNode=context.hoist(child.codegenNode)
hoistedCount++
}

5、如果子节点是组件,则递归调用 walk 函数,继续遍历子节点,以判断插槽中的节点及节点属性是否可以被提升。如下面的代码所示:

if(child.type===NodeTypes.ELEMENT){
//如果子节点的tagType是组件,则继续遍历子节点
//以判断插槽中的情况
constisComponent=child.tagType===ElementTypes.COMPONENT
if(isComponent){
context.scopes.vSlot++
}
//执行walk函数,继续判断插槽中的节点及节点属性是否可以被提升
walk(child,context)
if(isComponent){
context.scopes.vSlot--
}
}

6、如果节点上带有 v-for 指令或 v-if 指令,则递归调用 walk 函数,继续判断子节点是否可以被提升。如果 v-for 指令的节点只有一个子节点,v-if 指令的节点只有一个分支条件,则不进行提升。如下面的代码所示:

elseif(child.type===NodeTypes.FOR){
//Donothoistv-forsinglechildbecauseithastobeablock
//带有v-for指令的节点是一个Block
//如果v-for的节点中只有一个子节点,则不能被提升
walk(child,context,child.children.length===1)
}elseif(child.type===NodeTypes.IF){
//带有v-if指令的节点是一个Block
for(leti=0;i<child.branches.length;i++){
//Donothoistv-ifsinglechildbecauseithastobeablock
//如果只有一个分支条件,则不进行提升
walk(
child.branches[i],
context,
child.branches[i].children.length===1
)
}
}

walk 函数流程图

Vue3静态提升是什么

本文:Vue3静态提升是什么的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:Java一维数组和二维数组元素默认初始化值的判断方式是什么下一篇:

19 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18