vue3编译优化的内容有哪些


这篇“vue3编译优化的内容有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue3编译优化的内容有哪些”文章吧。

vue3编译优化有:1、引入了 patchFlag,用来标记动态内容;在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速diff算法。2、Block Tree。3、静态提升,是将静态的节点或者属性提升出去。4、预解析字符串化,当连续静态节点超过10个时,会将静态节点序列化为字符串。5、函数缓存;开启cacheHandlers选项后,函数会被缓存起来,后续可直接使用。

本文主要来分析 Vue3.0 编译阶段做的优化,在 patch 阶段是如何利用这些优化策略来减少比对次数。由于组件更新时依然需要遍历该组件的整个 vnode 树,比如下面这个模板:

<template><divid="container"><pclass="text">statictext</p><pclass="text">statictext</p><pclass="text">{{message}}</p><pclass="text">statictext</p><pclass="text">statictext</p></div></template>

整个 diff 过程如图所示:

可以看到,因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。

Vue.js 3.0 通过编译阶段对静态模板的分析,编译生成了 Block tree

Block tree 是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block treeVue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。

PatchFlag

由于 diff 算法无法避免新旧虚拟 DOM 中无用的比较操作,Vue.js 3.0 引入了 patchFlag,用来标记动态内容。在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速 diff 算法。PatchFlags 的所有枚举类型如下所示:

exportconstenumPatchFlags{TEXT=1,//动态文本节点CLASS=1<<1,//动态classSTYLE=1<<2,//动态stylePROPS=1<<3,//除了class、style动态属性FULL_PROPS=1<<4,//有key,需要完整diffHYDRATE_EVENTS=1<<5,//挂载过事件的STABLE_FRAGMENT=1<<6,//稳定序列,子节点顺序不会发生变化KEYED_FRAGMENT=1<<7,//子节点有key的fragmentUNKEYED_FRAGMENT=1<<8,//子节点没有key的fragmentNEED_PATCH=1<<9,//进行非props比较,ref比较DYNAMIC_SLOTS=1<<10,//动态插槽DEV_ROOT_FRAGMENT=1<<11,HOISTED=-1,//表示静态节点,内容变化,不比较儿子BAIL=-2//表示diff算法应该结束}

Block Tree

左侧的 template 经过编译后会生成右侧的 render 函数,里面有 _openBlock_createElementBlock_toDisplayString_createElementVNode(createVnode) 等辅助函数。

letcurrentBlock=nullfunction_openBlock(){currentBlock=[]//用一个数组来收集多个动态节点}function_createElementBlock(type,props,children,patchFlag){returnsetupBlock(createVnode(type,props,children,patchFlag));}exportfunctioncreateVnode(type,props,children=null,patchFlag=0){constvnode={type,props,children,el:null,//虚拟节点上对应的真实节点,后续diff算法key:props?.["key"],__v_isVnode:true,shapeFlag,patchFlag};...if(currentBlock&&vnode.patchFlag>0){currentBlock.push(vnode);}returnvnode;}functionsetupBlock(vnode){vnode.dynamicChildren=currentBlock;currentBlock=null;returnvnode;}function_toDisplayString(val){returnisString(val)?val:val==null?"":isObject(val)?JSON.stringify(val):String(val);}

此时生成的 vnode 如下:

此时生成的虚拟节点多出一个 dynamicChildren 属性,里面收集了动态节点 span

节点 diff 优化策略:

我们之前分析过,在 patch 阶段更新节点元素的时候,会执行 patchElement 函数,我们再来回顾一下它的实现:

constpatchElement=(n1,n2)=>{//先复用节点、在比较属性、在比较儿子letel=n2.el=n1.el;letoldProps=n1.props||{};//对象letnewProps=n2.props||{};//对象patchProps(oldProps,newProps,el);if(n2.dynamicChildren){//只比较动态元素patchBlockChildren(n1,n2);}else{patchChildren(n1,n2,el);//全量diff}}

我们在前面组件更新的章节分析过这个流程,在分析子节点更新的部分,当时并没有考虑到优化的场景,所以只分析了全量比对更新的场景。

而实际上,如果这个 vnode 是一个 Block vnode,那么我们不用去通过 patchChildren 全量比对,只需要通过 patchBlockChildren 去比对并更新 Block 中的动态子节点即可。由此可以看出性能被大幅度提升,从 tree 级别的比对,变成了线性结构比对。

我们来看一下它的实现:

constpatchBlockChildren=(n1,n2)=>{for(leti=0;i<n2.dynamicChildren.length;i++){patchElement(n1.dynamicChildren[i],n2.dynamicChildren[i])}}

属性 diff 优化策略:

接下来我们看一下属性比对的优化策略:

constpatchElement=(n1,n2)=>{//先复用节点、在比较属性、在比较儿子letel=n2.el=n1.el;letoldProps=n1.props||{};//对象letnewProps=n2.props||{};//对象let{patchFlag,dynamicChildren}=n2if(patchFlag>0){if(patchFlag&PatchFlags.FULL_PROPS){//对所props都进行比较更新patchProps(el,n2,oldProps,newProps,...)}else{//存在动态class属性时if(patchFlag&PatchFlags.CLASS){if(oldProps.class!==newProps.class){hostPatchProp(el,'class',null,newProps.class,...)}}//存在动态style属性时if(patchFlag&PatchFlags.STYLE){hostPatchProp(el,'style',oldProps.style,newProps.style,...)}//针对除了style、class的propsif(patchFlag&PatchFlags.PROPS){constpropsToUpdate=n2.dynamicProps!for(leti=0;i<propsToUpdate.length;i++){constkey=propsToUpdate[i]constprev=oldProps[key]constnext=newProps[key]if(next!==prev){hostPatchProp(el,key,prev,next,...)}}}if(patchFlag&PatchFlags.TEXT){//存在动态文本if(n1.children!==n2.children){hostSetElementText(el,n2.childrenasstring)}}}elseif(dynamicChildren==null){patchProps(el,n2,oldProps,newProps,...)}}}functionhostPatchProp(el,key,prevValue,nextValue){if(key==='class'){//更新classpatchClass(el,nextValue)}elseif(key==='style'){//更新stylepatchStyle(el,prevValue,nextValue)}elseif(/^on[^a-z]/.test(key)){//eventsaddEventListenerpatchEvent(el,key,nextValue);}else{//普通属性el.setAttributepatchAttr(el,key,nextValue);}}functionpatchClass(el,nextValue){if(nextValue==null){el.removeAttribute('class');//如果不需要class直接移除}else{el.className=nextValue}}functionpatchStyle(el,prevValue,nextValue={}){...}functionpatchAttr(el,key,nextValue){...}

总结: vue3 会充分利用 patchFlagdynamicChildren 做优化。如果确定只是某个局部的变动,比如 style 改变,那么只会调用 hostPatchProp 并传入对应的参数 style 做特定的更新(靶向更新);如果有 dynamicChildren,会执行 patchBlockChildren 做对比更新,不会每次都对 props 和子节点进行全量的对比更新。图解如下:

静态提升

静态提升是将静态的节点或者属性提升出去,假设有以下模板:

<div><span>hello</span><spana=1b=2>{{name}}</span><a><span>{{age}}</span></a></div>

编译生成的 render 函数如下:

exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",null,[_createElementVNode("span",null,"hello"),_createElementVNode("span",{a:"1",b:"2"},_toDisplayString(_ctx.name),1/*TEXT*/),_createElementVNode("a",null,[_createElementVNode("span",null,_toDisplayString(_ctx.age),1/*TEXT*/)])]))}

我们把模板编译成 render 函数是这个酱紫的,那么问题就是每次调用 render 函数都要重新创建虚拟节点。

开启静态提升 hoistStatic 选项后

const_hoisted_1=/*#__PURE__*/_createElementVNode("span",null,"hello",-1/*HOISTED*/)const_hoisted_2={a:"1",b:"2"}exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",null,[_hoisted_1,_createElementVNode("span",_hoisted_2,_toDisplayString(_ctx.name),1/*TEXT*/),_createElementVNode("a",null,[_createElementVNode("span",null,_toDisplayString(_ctx.age),1/*TEXT*/)])]))}

预解析字符串化

静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 10 个时,会将静态节点序列化为字符串。

假如有如下模板:

<div><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span></div>

开启静态提升 hoistStatic 选项后

const_hoisted_1=/*#__PURE__*/_createStaticVNode("<span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span>",10)const_hoisted_11=[_hoisted_1]exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",null,_hoisted_11))}

函数缓存

假如有如下模板:

<div@click="event=>v=event.target.value"></div>

编译后:

const_hoisted_1=["onClick"]exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",{onClick:event=>_ctx.v=event.target.value},null,8/*PROPS*/,_hoisted_1))}

每次调用 render 的时候要创建新函数,开启函数缓存 cacheHandlers 选项后,函数会被缓存起来,后续可以直接使用

exportfunctionrender(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",{onClick:_cache[0]||(_cache[0]=event=>_ctx.v=event.target.value)}))}

以上就是关于“vue3编译优化的内容有哪些”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注主机评测网行业资讯频道。


上一篇:vue适用多页面应用怎么实现

下一篇:vue sync出错如何解决


Copyright © 2002-2019 测速网 https://www.inhv.cn/ 皖ICP备2023010105号 城市 地区 街道
温馨提示:部分文章图片数据来源与网络,仅供参考!版权归原作者所有,如有侵权请联系删除!
热门搜索