记一次通过源码来看的性能优化(Vue)

318 阅读6分钟

前言

本文是作为作者近期进行的性能优化分析发现的实践性的优化点,不作为vue性能优化教程,但是也会分析一些优化的点

准备

如果你是熟读vue源码的或者是精通的,那么可以看一下优化目标就直接移步链接尝试一下优化

优化目标

  • 点击123重复数字块最顶部的递增数字变化不卡顿(注意getValue方法内部的阻塞循环代表着复杂的计算过程,不能够改变getValue方法的内部实现)

在线编辑尝试地址

  • 请不要保存组件,为了让大家都能够尝试优化
  • 在线地址

如果你是不了解vue的那么可以和我一起继续往下看进行代码的分析(为了后面我先简单叨两句原理吧)

响应原理

Object.defineProperty(data, key, {
  enumerable: true,
  configurable: false,
  get: function() {
  	console.log('我被获取了')
    return val;
  },
  set: function(newVal) {
    console.log('我被赋值了');
    val = newVal;
  }
});

通过defineProperty可以实现数据获取和重写加入额外的方法

渲染原理

const string = '<div>{{value}}</div>'; // 获取到渲染模板
const render = compileToFunctions(string); // 首先的到渲染函数render
const vnode = render(); // 获取到vnode
patch(vnode, oldVnode); //将vnode和老的vnode比对修改到

好的先简单知道这两个东西就行了,了解一个大概的概念即可

问题的出现

大家可以去打开在线地址,也可以直接看下面的代码

<template>
  <div class="hello" @click="addNum">
    {{haha}}
    <tr v-for="(item, index) in 10" :key="index">
      <td v-for="(item, index) in 5" :key="index">{{ getValue(index) }}</td>
    </tr>
  </div>
</template>

<script>
export default {
  name: 'BaseButton',
  data() {
    return {
      haha: 1
    }
  },
  methods: {
    addNum() {
      this.haha ++;
    },
    getValue(index) {
      const data = new Date().getTime();
      while (data + 50 > new Date().getTime()) {
          // 这个复杂函数里面需要index参数
      }
      return 12312312312
    }
  }
}
</script>

首先看一下这个代码,非常的easy就是有两个v-for的循环然后数据获取通过method来达到,而getValue中的阻塞循环则代表着我们在实际代码中的复杂耗时计算(当时这个方法内部实现不可修改)。

当我们进行渲染这个组件的时候会出现卡顿一下情况,而后页面一切正常。但是当我们进行点击的时候触发了addNum方法让haha这个数值+1,就会出现页面再一次出现了卡顿,过了两三秒后咋们的页面顶部的数字1才变成了2。

以上就是出现问题的流程,如果不太理解可以去在线尝试一下点击(注意不要点太多次,否则你会被卡死)

那么接下来就是我们的分析时间了。

原因

在这咋们就一步步的分析整个执行的过程。

  • 首先页面初始化渲染没什么说的,获取数据导致卡顿了一下
  • 当你点了一下后,haha数值被修改了触发了前面方法里的set,然后set里面又有额外的方法让我们的组件跟新一下改变的东西
  • 然后就来到了渲染原理,通过render函数重新生成了vnode比对后跟新了咋们的1变成了2
    • 在这个步骤里面需要注意的是vnode的生成,它会导致我们整个template进行一次虚拟的计算(其实就是循环也被计算了,getValue也被循环执行过了)

通过上面简单的分析可以得到当我们的数值更新后会导致整个组件被重新计算vnode,虽然最后经过vnode的diff比对并循环的内容并没有被更新,但任然经过了计算。

真正原理总结: 在vue中每一个实例都对应着相应的响应数据,当任意一个响应数据被更新后(在这个例子中就是haha数值增加1),会导致整个实例进行vnode(虚拟dom构建),最后再进行比对diff比对不同跟新到dom节点上。然后再由此可以得知,虽然我们的循环没有任何的变化,但是处于相同组件中,就会导致计算(相应在这)就会触发getValue方法引起阻塞,虽然执行完后dom节点没有变化并没有跟新,但是已经计算过了。

解决

其实由上面的原因就可以得出,当我的haha被更新时不重新计算咋们循环的vnode就可以了。

然后由现在得到原理知识扩展一下可以找到解决方法:将不想要被变化影响的内容单独抽离成组件

所以代码就变成了这样的:

// 功能组件
<template>
  <div class="hello" @click="addNum">
    {{haha}}
    <list></list>
  </div>
</template>

<script>
export default {
  name: 'BaseButton',
  data() {
    return {
      haha: 1
    }
  },
  methods: {
    addNum() {
      this.haha ++;
    }
  }
}
</script>

// 列表组件
<template>
    <section>
    	<tr v-for="(item, index) in 10" :key="index">
            <td v-for="(item, index) in 5" :key="index">{{ getValue(index) }}</td>
    	</tr>
    </section>
</template>

<script>
export default {
  name: 'list',
  methods: {
    getValue(index) {
      const data = new Date().getTime();
      while (data + 50 > new Date().getTime()) {
          // 这个复杂函数里面需要index参数
      }
      return 12312312312
    }
  }
}
</script>

当我们把代码改成这样后,咋们的list组件由于数据没有跟新所以并不会重新生成vnode,同时也就不会触发getValue方法导致阻塞了

额外分析

到这可能大家就感觉在vue中应该多抽离组件来减少计算量了,但是如果对组件抽离的过细除了代码理解上的问题、维护的问题之外性能方面还是有问题的

就当前这个问题,如果我们将while取消掉,那么这个时候单纯从性能问题上说我们该怎么做才是最好的呢?

接下来我们就来简单说一下,在初始化渲染的时候:

  • 单组件
    • render函数只有一个,所以挂载更快
    • 更新虽然会读取藏数值但是还是会触发value的get方法
  • 多组件
    • render函数增加,渲染挂载稍微慢一点点(如果数量级差距较多也会有影响)
    • 更新无影响,所以更新更快

所以并没有哪个方案是绝对完全的,只有大家在开发的时候进行不断的分析得到相对最优解而已,在合理合适的情况下进行合理合适的组件抽分才能达到用户体验最佳的性能优化

最后

本人是一个喜好造轮子的前端开发者,如果感兴趣可以看看其他文章,本文只是一个工作的分析而已。

在这还是打一个广告:下一篇主要文章为git自动化开发提交工具 现阶段开发最基础功能已经完成,欢迎大家去下载下来进行玩哦,有什么问题可以问我。待开发完成后我会继续完成文章,来继续做开源。

npm地址: www.npmjs.com/package/laz…

安装方式:npm install -g lazy-git-cli (在npm中有说明哦)