vue前端性能优化有哪些方法,vue性能调优

  vue前端性能优化有哪些方法,vue性能调优

  本文主要针对Vue.js 2.x版本。毕竟Vue.js 2.x在未来一段时间内还是我们工作中的主流版本。对vue.js性能优化技巧感兴趣的朋友来看看吧。

  

目录

   Functional components child component splitting local variables reuse DOM with v-showkeepalivedefered features time slicing non-reactive data virtual scrolling Summary Reference本文主要参考了Vue.js的核心成员Guillaume Chau在美国Vue conf上19:9 Performance secrets disclosed中分享的主题,分享中提到了Vue.js的9种性能优化技术。

  看了他分享的PPT,也看了相关的项目源代码。在深刻理解了它的优化原理后,我将一些优化技巧运用到了平时的工作中,取得了相当不错的效果。

  这个分享很实用,但是好像知道和关注的人不多。到目前为止,这个项目只有几百个穷明星。虽然《大老板》的分享已经过去两年了,但是优化技巧并没有过时。为了让更多的人了解和学习实用的技术,我决定对他的分享做二次加工,阐述优化原理,并进行一定程度的拓展和延伸。

  本文主要针对Vue.js 2.x版本。毕竟Vue.js 2.x在未来一段时间内还是我们工作中的主流版本。

  我建议大家在学习这篇文章的时候,可以拉一下项目的源代码,在本地运行一下,看看优化前后的效果差异。

  

Functional components

  第一个技巧,功能组件,你可以查看这个在线例子。

  优化前的组件代码如下:

  模板

  div class=cell

  div v-if=value class=on/div

  第五节-else class= off /节

  /div

  /模板

  脚本

  导出默认值{

  道具:[值],

  }

  /脚本

  优化后的组件代码如下:

  模板功能

  div class=cell

  div v-if= props . value class= on /div

  第五节-else class= off /节

  /div

  /模板

  然后我们在每个父组件中渲染优化前后的800个组件,通过修改每一帧中的数据来触发组件的更新。我们打开Chrome的性能面板记录他们的性能,得到如下结果。

  优化前:

  优化后:

  对比这两张图,可以看出优化前执行脚本比优化后要多花时间,而且我们知道JS引擎是单线程运行机制,JS线程会阻塞UI线程,所以当脚本执行时间过长时,就会阻塞渲染,造成页面卡顿。优化后的脚本执行时间短,所以性能更好。

  那么,为什么带有功能组件的JS执行时间变短了呢?这要从功能组件的实现原理说起。你可以把它理解为一个函数,可以根据你传递的上下文数据渲染生成一片DOM。

  功能组件不同于常见的对象类型组件,它不会被视为真正的组件。我们知道,在打补丁过程中,如果一个节点是组件vnode,子组件的初始化过程就会递归执行;而功能组件的渲染生成普通的vnode,没有递归子组件的过程,渲染开销会低很多。

  因此,功能组件不会有状态、响应数据和生命周期挂钩功能。你可以把它看作是从一个通用的组件模板中剥离出一部分DOM,通过函数来渲染,这是一种DOM级别的复用。

  

Child component splitting

  第二个技巧,子组件拆分,你可以查看这个在线例子。

  优化前的组件代码如下:

  模板

  div:style= { opacity:number/300 }

  div{{ heavy() }}/div

  /div

  /模板

  脚本

  导出默认值{

  道具:[数字],

  方法:{

  重型(){

  常数n=100000

  让结果=0

  for(设I=0;I n;i ) {

  result=math . sqrt(math . cos(math . sin(42))。

  }

  回送结果

  }

  }

  }

  /脚本

  优化后的组件代码如下:

  模板

  div:style= { opacity:number/300 }

  ChildComp/

  /div

  /模板

  脚本

  导出默认值{

  组件:{

  子组件:{

  方法:{

  重型(){

  常数n=100000

  让结果=0

  for(设I=0;I n;i ) {

  result=math . sqrt(math . cos(math . sin(42))。

  }

  回送结果

  },

  },

  渲染(h) {

  返回h(div ,this.heavy())

  }

  }

  },

  道具:[数字]

  }

  /脚本

  然后我们在每个父组件中渲染优化前后的300个组件,在每一帧中,我们修改数据来触发组件的更新。我们打开Chrome的性能面板记录他们的性能,得到如下结果。

  优化前:

  优化后:

  对比这两张图,可以看到优化后的脚本执行时间明显少于优化前,所以性能体验更好。

  那么为什么会有区别呢?让我们看看优化前的组件。示例通过一个重函数模拟一个耗时的任务,这个函数每渲染一次就执行一次,所以每次渲染组件都要花很长时间执行JavaScript。

  优化的方式是用ChildComp封装这个耗时任务的执行逻辑,这个繁重的函数。因为Vue的更新是组件粒度的,虽然每一帧都是父组件通过数据修改重新渲染的,但是ChildComp不会重新渲染,因为它内部没有响应的数据变化。因此,优化后的组件不会在每次渲染时都执行耗时的任务,自然执行的JavaScript时间会减少。

  但是,我对这种优化方法提出了一些不同的看法。详情请点击本期。我认为在这种情况下,使用计算属性进行优化比拆分子组件更好。由于计算属性本身的缓存属性,耗时的逻辑将只在第一次呈现时执行,使用计算属性呈现子组件没有额外的开销。

  在实际工作中,会有很多使用计算属性来优化性能的场景。毕竟也体现了一种空间换时间的优化思路。

  

Local variables

  第三个技巧,局部变量,你可以看看这个网上的例子。

  优化前的组件代码如下:

  模板

  div:style= { opacity:start/300 } { { result } }/div

  /模板

  脚本

  导出默认值{

  道具:[开始],

  计算值:{

  base () {

  返回42

  },

  结果(){

  让结果=this.start

  for(设I=0;i 1000i ) {

  result=math . sqrt(math . cos(math . sin(this . base)))this . base * this . base this . base . base * 2 this . base * 3

  }

  回送结果

  },

  },

  }

  /脚本

  优化后的组件代码如下:

  模板

  div:style= { opacity:start/300 } { { result } }/div

  /模板

  脚本

  导出默认值{

  道具:[开始],

  计算值:{

  base () {

  返回42

  },

  结果({ base,start }) {

  让结果=开始

  for(设I=0;i 1000i ) {

  result=math . sqrt(math . cos(math . sin(base)))base * base base base * 2 base * 3

  }

  回送结果

  },

  },

  }

  /脚本

  然后我们在每个父组件中渲染优化前后的300个组件,在每一帧中,我们修改数据来触发组件的更新。我们打开Chrome的性能面板记录他们的性能,得到如下结果。

  优化前:

  优化后:

  对比这两张图,可以看到优化后的脚本执行时间明显少于优化前,所以性能体验更好。

  这主要是优化前后组件的计算属性结果实现的差异。优化前,组件在计算过程中多次访问this.base,而优化后,组件在计算前用本地变量base缓存this.base,然后直接访问base变量。

  那么为什么这种差异会造成性能的差异呢?原因是每次访问this.base,因为this.base是一个响应式对象,它的getter会被触发,然后执行依赖集合的相关逻辑代码。类似的逻辑执行得更频繁。如示例中,数百个组件循环更新,每个组件触发计算的重新计算,然后依赖关系收集的相关逻辑被多次执行,因此性能自然下降。

  从需求的角度来说,this.base进行一次依赖收集就足够了。所以我们只需要把它的getter求值结果返回给局部变量base。当我们再次访问该库时,getter不会被触发,依赖集合的逻辑也不会被遵循,性能自然会得到提升。

  这是一种非常实用的性能优化技术。因为在开发Vue.js项目的时候,很多人习惯性的一取变量就直接写this.xxx,因为大多数人不会注意到访问this.xxx背后做了什么,当访问次数少的时候,性能问题并不突出,但是一旦访问次数增加,比如一个大周期内多次访问,比如示例场景,性能问题就会出现。

  我之前对ZoomUI的表格组件进行性能优化的时候,在渲染表体的时候使用了局部变量的优化技术,并且写了一个性能对比的基准:渲染一个1000 * 10的表格,重新渲染ZoomUI表格更新数据的性能是ElementUI表格的近一倍。

  

Reuse DOM with v-show

  第四招,用v-show复用DOM,可以查一下这个网上的例子。

  优化前的组件代码如下:

  模板功能

  div class=cell

  div v-if=props.value class=on

  Heavy :n=10000/

  /div

  第五节-else class=off

  Heavy :n=10000/

  /部分

  /div

  /模板

  优化后的组件代码如下:

  模板功能

  div class=cell

  div v-show= props . value class= on

  Heavy :n=10000/

  /div

  第五节-show=!props.value class=off

  Heavy :n=10000/

  /部分

  /div

  /模板

  然后我们在每个父组件中渲染优化前后的200个组件,在每一帧中,我们修改数据来触发组件的更新。我们打开Chrome的性能面板记录他们的性能,得到如下结果。

  优化前:

  优化后:

  对比这两张图,可以看到优化后的脚本执行时间明显少于优化前,所以性能体验更好。

  优化前后的主要区别是用v-show指令代替v-if指令来代替组件的显示和隐藏。虽然v-show和v-if在性能上差不多,都是控制组件的显示和隐藏,但是在内部实现上还是有很大差距的。

  v-if指令将在编译阶段编译成三元运算符,并进行条件渲染。例如,优化前的组件模板被编译以生成以下渲染函数:

  函数render() {

  用(这个){

  return _c(div ,{

  staticClass:“单元”

  },[(props.value)?_c(div ,{

  staticClass:“开”

  },[_c(重,{

  属性:{

   n: 1万

  }

  })],1) : _c(section ,{

  staticClass:“关闭”

  },[_c(重,{

  属性:{

   n: 1万

  }

  })], 1)])

  }

  }

  当条件props.value的值发生变化时,会触发相应的组件更新。对于v-if渲染节点,由于新旧节点的不一致,在核心diff算法比较的过程中,会去掉旧的vnode节点,创建一个新的vnode节点,然后创建一个新的重组件,并且会经历初始化、渲染vnode和重组件本身补丁的过程。

  所以每次用v-if更新一个组件,都会产生一个新的重子组件。当更多组件更新时,自然会造成性能压力。

  而当我们使用v-show指令时,优化后的组件模板被编译生成如下的渲染函数:

  函数render() {

  用(这个){

  return _c(div ,{

  staticClass:“单元”

  },[_c(div ,{

  指令:[{

  名称:显示,

  raw name:“v-show”,

  值:(props.value),

  表达式:“props.value”

  }],

  staticClass:“开”

  },[_c(重,{

  属性:{

   n: 1万

  }

  })],1),_c(section ,{

  指令:[{

  名称:显示,

  raw name:“v-show”,

  值:(!props.value),

  表情:!道具.价值

  }],

  staticClass:“关闭”

  },[_c(重,{

  属性:{

   n: 1万

  }

  })], 1)])

  }

  }

  当条件props.value的值发生变化时,会触发相应的组件更新。对于v-show渲染的节点,由于新旧Vnode一致,所以只需要一直修补vnode即可。那么它是如何让DOM节点显示和隐藏的呢?

  在最初的patchVnode进程中,v-show指令对应的hook函数会在内部更新,然后它会根据v-show指令绑定的值来设置它所作用的DOM元素的style.display的值,从而控制显示和隐藏。

  所以相对于v-if不断删除和新建函数的DOM,v-show只更新已有DOM的显式和隐式值,所以v-show的开销比v-if小很多,而且其内部DOM结构越复杂,性能差别越大。

  但v-show相比v-if的性能优势是在组件的更新阶段。如果只是在初始化阶段,v-if的性能要高于v-show,因为它只能渲染一个分支,而v-show渲染两个分支,通过style.display控制对应DOM的显示和隐藏

  使用v-show时,会渲染分支中的所有组件,并执行相应的生命周期钩子函数,而使用v-if时,不会渲染没有命中的分支中的组件,也不会执行相应的生命周期钩子函数。

  因此,你必须了解它们的原理和区别,然后才能在不同的情况下使用适当的说明。

  

KeepAlive

  第五种技术,用KeepAlive组件缓存DOM,可以查看这个在线例子。

  优化前的组件代码如下:

  模板

  div id=应用程序

  路由器-视图/

  /div

  /模板

  优化后的组件代码如下:

  模板

  div id=应用程序

  点火电极

  路由器-视图/

  /保持活力

  /div

  /模板

  当我们点击按钮在简单页面和重度页面之间切换时,会渲染出不同的视图,重度页面的渲染非常耗时。我们打开Chrome的性能面板记录它们的性能,然后分别在优化前后执行上述操作,会得到如下结果。

  优化前:

  优化后:

  对比这两张图,可以看到优化后的脚本执行时间明显少于优化前,所以性能体验更好。

  在非优化场景下,我们每次点击按钮切换路由视图,都会重新渲染组件,渲染后的组件会经历组件初始化、渲染、补丁等过程。如果组件很复杂或者嵌套很深,整个渲染将会花费很长时间。

  使用KeepAlive后,KeepAlive包装的组件的vnode和DOM会在第一次渲染后缓存,然后下次再次渲染组件时,直接从缓存中获取对应的vnode和DOM,然后进行渲染,无需再经过组件初始化、渲染、打补丁等一系列过程,减少了脚本的执行时间,性能更好。

  但是使用KeepAlive组件并不是没有成本的,因为它会占用更多的内存进行缓存,这是空间换时间优化思想的典型应用。

  

Deferred features

  第六个技巧,使用Deferred component延迟批量渲染组件,可以查看一下这个在线例子。

  优化前的组件代码如下:

  模板

  div class=延期关闭

  vue icon icon= fitness _ center class= gigant /

  h2我是一个沉重的页面/h2

  粗v-for=n in 8 :key=n/

  Heavy class=超重:n=9999999/

  /div

  /模板

  优化后的组件代码如下:

  模板

  div class=延迟开启

  vue icon icon= fitness _ center class= gigant /

  h2我是一个沉重的页面/h2

  模板v-if=defer(2)

  粗v-for=n in 8 :key=n/

  /模板

  Heavy v-if=defer(3) class=超重:n=9999999/

  /div

  /模板

  脚本

  从“@/mixins/Defer”导入延迟

  导出默认值{

  mixins: [

  Defer(),

  ],

  }

  /脚本

  当我们点击按钮在简单页面和重度页面之间切换时,会渲染出不同的视图,重度页面的渲染非常耗时。我们打开Chrome的性能面板记录它们的性能,然后分别在优化前后执行上述操作,会得到如下结果。

  优化前:

  优化后:

  对比这两张图可以发现,在优化之前,当我们从简单页面切入到重页面的时候,当一个渲染接近尾声的时候,页面还是由简单页面进行渲染,会给人一种页面卡顿的感觉。优化后,当我们从简单页面切入到重页面时,页面已经在渲染前的位置渲染过一次,重页面是逐渐渲染的。

  优化前后的差距主要是后者使用了Defer这个mixin,那么具体是怎么操作的呢?让我们来看看:

  导出默认函数(计数=10) {

  返回{

  data () {

  返回{

  显示优先级:0

  }

  },

  已安装(){

  this.runDisplayPriority()

  },

  方法:{

  runDisplayPriority () {

  常数步长=()={

  requestAnimationFrame(()={

  this . display优先级

  if (this.displayPriority count) {

  步骤()

  }

  })

  }

  步骤()

  },

  延期(优先){

  返回这个。displayPriority=priority

  }

  }

  }

  }

  延迟的主要思想是将一个组件的渲染分成多次。它在内部维护displayPriority变量,然后通过requestAnimationFrame在每一帧中增加自己,最多加到计数中。那么当displayPriority通过v-if=defer(xxx)增加到xxx时,使用Defer mixin的组件可以控制一些块的呈现。

  当你有需要花费时间渲染的组件时,使用Deferred进行渐进式渲染是个不错的主意,可以避免因为JS执行时间太长而导致渲染一次卡顿的现象。

  

Time slicing

  第七个技巧,使用时间切片时间切片切割技术,可以查一下这个网上的例子。

  优化前的代码如下:

  fetchItems ({ commit },{ items }) {

  提交(“clearItems”)

  提交(“添加项”,项)

  }

  优化后的代码如下:

  fetchItems ({ commit },{ items,splitCount }) {

  提交(“clearItems”)

  const queue=新作业队列()

  splitArray(items,splitCount)。forEach(

  chunk=queue.addJob(done={

  //按时间片提交数据。

  requestAnimationFrame(()={

  提交(“添加项”,块)

  完成()

  })

  })

  )

  await queue.start()

  }

  首先,我们通过单击Genterate items按钮创建10,000条假数据,然后分别在时间切片打开和关闭时单击Commit items按钮提交数据,并打开Chrome的Performance面板记录它们的性能。我们将得到以下结果。

  优化前:

  优化后:

  对比这两张图,可以发现优化前的脚本执行总时间比优化后的少。但从实际感知来看,优化前点击提交按钮后,页面会卡死1.2秒左右。优化后页面不会完全卡顿,但是在渲染上还是会有卡顿的感觉。

  那么为什么优化前页面会卡死呢?由于一次提交的数据太多,内部JS执行时间过长,阻塞了UI线程,导致页面卡顿。

  优化后,页面仍然有堵塞,因为我们拆分数据的粒度是1000块。在这种情况下,仍然有重新渲染组件的压力。我们观察fps才十几,会有卡壳的感觉。通常只要页面的fps达到60,页面就会非常流畅。如果我们把数据拆分的粒度改为100片,基本上fps可以达到50片以上。虽然页面渲染变得更加流畅,但是完成10000条数据的总提交时间还是比较长。

  使用时间切片技术可以避免页面卡顿。通常,我们会在这种耗时的任务处理中添加一个加载效果。在这个例子中,我们可以打开加载动画,然后提交数据。通过对比发现,在优化之前,JS已经运行了很长时间,阻塞了UI线程,这个加载动画就不会显示了。优化后,由于我们拆分成多个时间片提交数据,单个JS运行时间缩短,这样加载动画就有机会显示出来。

  这里需要注意的一点是,虽然我们使用requestAnimationFrame API来拆解时间片,但是使用RequestAnimated Frame本身并不能保证全帧运行。RequestAnimated Frame保证相应的传入回调函数将在浏览器每次重绘后执行。为了保证满帧,JS一个Tick只能运行不超过17ms。

  

Non-reactive data

  第八个技巧,使用非反应性数据,你可以检查这个在线例子。

  预优化代码如下:

  const data=items.map(

  item=({

  id: uid,

  数据:项目,

  投票:0

  })

  )

  优化后的代码如下:

  const data=items.map(

  item=optimizeItem(item)

  )

  函数优化项(项目){

  常量项目数据={

  id: uid,

  投票:0

  }

  Object.defineProperty(itemData, Data ,{

  //标记为非反应性

  可配置:假,

  值:项目

  })

  返回项目数据

  }

  和前面的例子一样,我们先通过点击Genterate items按钮创建10000条假数据,然后分别在部分反应性开启和关闭时点击Commit items按钮提交数据,打开Chrome的性能面板记录它们的性能。我们将得到以下结果。

  优化前:

  优化后:

  对比这两张图,可以看到优化后的脚本执行时间明显少于优化前,所以性能体验更好。

  这种差异的原因是,当内部提交数据时,新提交的数据也将被默认定义为有响应的。如果数据的子属性是对象的形式,子属性也将变得递归响应。因此,当提交大量数据时,这个过程就变成了一个耗时的过程。

  优化后,我们将新提交的数据中的对象属性数据手动更改为可配置的false,这样通过Object.keys(obj)内部获取对象属性数组将忽略该数据,并且它不会为属性数据定义反应性。由于数据指向一个对象,这样也会减少递归响应的逻辑,相当于减少了这部分的性能损失。数据量越大,这种优化的效果就越明显。

  其实像这样的优化方式有很多,比如我们在组件中定义的一些数据,并不是所有的都要在数据中定义。模板中有些数据我们不用,也不需要监控它的变化。我们只想在组件的上下文中共享这些数据。此时,我们可以将这些数据装载到组件实例上,例如:

  导出默认值{

  已创建(){

  this.scroll=null

  },

  已安装(){

  this.scroll=new BScroll(this。$el)

  }

  }

  以便我们可以在组件上下文中共享滚动对象,即使它不是响应对象。

  

Virtual scrolling

  第九个技巧,使用虚拟滚动,你可以查看这个在线例子。

  预优化组件的代码如下:

  div class=项目编号-v

  FetchItemViewFunctional

  v-for=项目中的项目

  :key=item.id

  :item=item

  @vote=voteItem(item)

  /

  /div

  优化后的代码如下:

  循环滚动器

  class=items

  :items=items

  :item-size=24

  模板v-slot=“{ item }”

  FetchItemView

  :item=item

  @vote=voteItem(item)

  /

  /模板

  /recycle-滚动条

  和前面的例子一样,我们需要打开视图列表,然后点击Genterate items按钮,创建10000条假数据(注意在线例子最多只能创建1000条数据,但实际上1000条数据并不能很好的体现优化效果,所以我修改了源代码的限制,在本地运行,创建了10000条数据)。然后分别在未优化和RecycleScroller的情况下点击Commit items按钮提交数据,滚动页面,打开Chrome的性能面板记录它们的性能。您将获得以下结果。

  优化前:

  优化后:

  对比这两张图,我们发现在非优化的情况下,一万条数据的fps在滚动的情况下只有个位数,在不滚动的情况下只有十几。原因是非优化场景需要渲染的DOM太多,渲染本身的压力就很大。优化后,即使有10000条数据,在滚动的情况下fps也可以超过30,在不滚动的情况下可以达到60全帧。

  这种差异是由于虚拟滚动的实现:仅在视口中呈现DOM。这样渲染的DOM总量很小,自然性能会好很多。

  虚拟组件也是Guillaume Chau写的,有兴趣的同学可以研究一下它的源代码实现。它的基本原理是监控滚动事件,动态更新要显示的DOM元素,计算它们在视图中的位移。

  虚拟滚动组件并不是没有成本的,因为滚动时需要实时计算,所以会有一定的脚本执行成本。所以,如果列表的数据量不是很大,我们用普通的滚动就够了。

  

总结

  通过这篇文章,希望你能了解到Vue.js的9种性能优化技术,并应用到实际的开发项目中。除了以上技术,还有懒加载图片、懒加载组件、异步组件等常见的性能优化方法。

  在做性能优化之前,我们需要分析性能的瓶颈,这样才能因地制宜。另外,性能优化需要数据支持。在你做任何性能优化之前,你需要收集优化前的数据,这样你就可以通过优化后的数据对比看到优化的效果。

  希望在以后的开发过程中,你不要仅仅满足于实现需求,还要在编写每一行代码的时候考虑可能的性能影响。

  

参考资料

  [1]vue-9-perf-secrets slidse:https://slides.com/akryum/vueconfus-2019

  [2] vue-9-perf-secrets分享演讲视频:3359 www . vue master . com/conferences/vue conf-us-2019/9-perf-secrets-discovered/

  [3] vue-9-perf-secrets项目源代码:https://github.com/Akryum/vue-9-perf-secrets

  [4] vue-9-perf-secrets在线演示地址:https://vue-9-perf-secrets.netlify.app/

  [5]vue-9-perf-秘密讨论问题:https://github.com/Akryum/vue-9-perf-secrets/issues/1

  [6]Vue-Virtual-Scroller项目源代码:https://github.com/Akryum/vue-virtual-scroller

  关于Vue.js的九个性能优化技巧的这篇文章到此为止(值得收藏)。有关Vue.js性能优化技术的更多信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: