性能优化最多见的落脚点是在网络和dom上,可是在大数据量的场景下,因为Vue自己的特性,可能会形成js运行层面的性能问题,这篇文章讨论的就是针对这一部分的性能优化方案。javascript
// App.vue <template> <div> <p>It's {{ firstUser.name }}'s show time</p> <div>total: {{ total }}</div> </div> </template> <script> const user = [] let i = 0 while (i++ < 50000) { user.push({ id: i, age: 18, name: `kunkun_${i}`, alais: 'Irving', gender: 'female', education: 'senior high school', height: 'xxx', weight: 'xxx', hobby: 'xxx', tag: 'xxx', skill: { sing: 0, dance: 0, rap: 0, basketball: 100, }, }) } export default { data: { userList: user, }, computed: { firstUser() { const userList = this.userList return userList.length ? userList[0] : {} }, total() { return this.userList.length }, }, } 复制代码
如以上代码所示,模拟了5万个用户,每个用户拥有id, name, age等等字段。 jsfiddlehtml
打开chrome devtool的Performance工具,能够看到,渲染这个组件的过程当中,Observer这个阶段耗时2.19s(测试机器配置为桌面端i7,16g内存)。 vue
接下来,我会一步一步的把这一段耗时减小到10ms。java
众所周知,Vue在渲染组件的时候,会对data对象进行改造,遍历data的key,调用defineProperty方法定义它的setter和getter。若是某个字段是Object,或者Array,还会递归的对这个字段进行上诉操做。chrome
一般状况下,这个操做耗时是很短的,可是当数据量很是大的时候,对每个数据项的每个字段都进行defineProperty的操做就是一个昂贵的操做,因此性能优化的出发点就是减小defineProperty的次数。api
在这个模拟的例子当中,其实我只须要2个字段,一个是name,一个是id(id甚至也能够不要),因此我把多余的字段都去掉,一共减小了8个String类型的字段,和一个Object类型的字段,能够减小 (8 + 4) * n次defineProperty操做和n次递归调用。看看结果如何。 数组
Observer这个操做从2.2s减小到了515ms,提高仍是比较大的。性能优化
在当前版本(2.x)的Vue当中,对于数据变更的检测有许多限制,好比不能检测对象属性的添加和删除;不能检测到经过数据索引直接设置数据项等等。markdown
因此,当一个数组的数据项都是基本数据类型的时候,Vue不会进行任何操做。网络
首先,把user数据拍扁
const user = [] let i = 0 while (i++ < 50000) { user.push(`kun_${i}`, i) // 经过index为基数仍是偶数分辨是name仍是id } 复制代码
而后,相应的改变computed的计算方法,不影响渲染逻辑和业务逻辑
... computed: { firstUser() { const userList = this.userList return userList.length ? { name: userList[0], id: userList[1] } : {} }, total() { return this.userList.length / 2 }, } ... 复制代码
看看结果如何
到此为止,性能上的问题已经解决了,可是扁平的数据会影响业务代码的开发效率和可读性,同时数据和它的index产生了深耦合,若是咱们须要添加一个字段使用或者改变下顺序,很容易出问题。 不过,咱们能够利用computed计算属性把已经被拍扁的数据从新组装起来。因为Vue的响应式数据改造只针对data选项和props选项,不包括computed,因此只会产生不多的函数运行耗时。
export default { data() { return { // 扁平的数据存起来 originSserList: user, } }, computed: { firstUser() { const userList = this.userList return userList.length ? userList[0] : {} }, total() { return this.userList.length }, // 从新'组装'便于使用的计算属性,不影响本来的渲染和业务逻辑 userList() { const result = [] const user = this.originSserList for(let i = 0; i < user.length; i += 2) { const name = user[i] const id = user[i + 1] result.push({ name, id }) } return result }, }, } 复制代码
看看这种状况下的Performance。
到这里,在无需改动任何的渲染逻辑和业务逻辑的状况下,将js的运行时间从2.2s减小到了10ms左右,提高了200倍。而且这些数据是在桌面端i7处理器下获得的,大大超越了绝大部分的用户的机器性能,更不用说移动端了,因此在实际的大数据量场景下,能取得更加明显的用户可感知的性能提高。
没想到吧,还有Step4?已经没有优化空间了呀。
在这个模拟的场景里面,确实没有优化的空间了,不过,并非全部的数据均可以很好的进行扁平化处理,这可能涉及到方方面面的缘由与权衡。那么这种状况下,如何进行优化呢?
一般在Vue组件当中,都是把数据放在data选项当中,Vue会对data选项中的数据进行响应式改造,我称之"动态数据"或者"响应式"数据,可是并非全部的数据都是会发生变化的,不少时候,特别是大数据量场景下的数据是不会或者不多发生变化的,这种状况下,就没有必要把它放到data选项中去,而是在beforeCreated当中进行数据初始化,也不会影响数据的使用。
beforeCreated() { this.userList = xxx // 记得把data当中的userList删掉 } 复制代码
这种处理方式,我称之为数据静态化,这种数据,我称之为"静态数据"。
可是,有一点须要特别的注意,静态数据并不在Vue的响应式系统当中,也就是说当你进行this.userList = newUserList时,视图不会从新渲染,对应的computed计算属性也不会从新计算。没有了Vue提供的响应式系统,若是数据变更的时候,咱们须要手动的去计算对应的数据,可能还须要配合$forceUpdate这个api去从新渲染视图。此时,须要在性能和代码可读性与开发效率上进行取舍与权衡。
因为Vue的响应式系统,大数据量场景下可能会形成js运行层面的性能问题,能够经过3个方法去解决
这3个方法相互并不冲突,能够根据实际状况选择其中的1种或多种方法进行组合。