运行时性能主要关注 Vue 应用初始化之后对 CPU、内存、本地存储等资源的占用,以及对用户交互的及时响应。下面是一些有用的优化手段
v-show,v-if 用哪个
在我来看要分两个维度去思考问题,第一个维度是权限问题,只要涉及到权限相关的展示无疑要用 v-if
,第二个维度在没有权限限制下根据用户点击的频次选择,频繁切换的使用 v-show
,不频繁切换的使用 v-if
,这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if
,因为减少了 dom 数量,加快首屏渲染,至于性能方面我感觉肉眼看不出来切换的渲染过程,也不会影响用户的体验。
减少模板里面的表达式与判断
不要在模板里面写过多的表达式与判断 v-if="isShow && isAdmin && (a || b)"
,这种表达式虽说可以识别,但是不是长久之计,当看着不舒服时,适当的写到 methods 和 computed 里面封装成一个方法,这样的好处是方便我们在多处判断相同的表达式,其他权限相同的元素再判断展示的时候调用同一个方法即可。
循环调用子组件时添加 key
key 可以唯一标识一个循环个体,可以使用例如 item.id
作为 key,假如数组数据是这样的 ['a' , 'b', 'c', 'a']
,使用 :key="item"
显然没有意义,更好的办法就是在循环的时候 (item, index) in arr
,然后 :key="index"
来确保 key 的唯一性。
引入生产环境的 Vue 文件去除无用的语句
开发环境下,Vue 会提供很多警告来帮你对付常见的错误与陷阱。而在生产环境下,这些警告语句没有用,反而会增加应用的体积。有些警告检查还有一些小的运行时开销。
当使用 webpack 或 Browserify 类似的构建工具时,Vue 源码会根据 process.env.NODE_ENV 决定是否启用生产环境模式,默认情况为开发环境模式。在 webpack 与 Browserify 中都有方法来覆盖此变量,以启用 Vue 的生产环境模式,同时在构建过程中警告语句也会被压缩工具去除。
详细的做法请参阅 生产环境部署
模板预编译
当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。
预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。
如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader,它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数。
提取组件的 CSS 到单独到文件
当使用单文件组件时,组件内的 CSS 会以 <style>
标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存
查阅这个构建工具各自的文档来了解更多:
- webpack + vue-loader (
vue-cli
的 webpack 模板已经预先配置好) - Browserify + vueify
- Rollup + rollup-plugin-vue
利用Object.freeze()
提升性能
Object.freeze()是ES5新增的特性,可以冻结一个对象,防止对象被修改。也意味着响应系统无法再追踪变化。
vue 1.0.18+对其提供了支持,对于data或vuex里使用freeze冻结了的对象,vue不会做getter和setter的转换。
Vue observer 源码
应用场景
如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升。在我的实际开发中,这种提升大约有5~10倍,倍数随着数据量递增
对于纯展示的大数据,都可以使用Object.freeze提升性能。
简单的使用示例
<template>
<div>
<p v-for="(item, index) in list" :key="index">{{ item.value }}</p>
<button v-on:click="partUpdate">局部更新</button>
<button v-on:click="allUpdate">全部更新</button>
</div>
</template>
<script>
export default {
name: "Freeze",
data () {
return {
// vue不会对list里的object做getter、setter绑定
list: Object.freeze([
{ value: 1 },
{ value: 2 }
])
}
},
methods: {
partUpdate () {
// 界面不会有响应
this.list[0].value = 100
},
allUpdate () {
// Object.freeze()冻结的是值,你仍然可以将变量的引用替换掉
// 下面两种做法,界面都会响应
// this.list = [
// { value: 100 },
// { value: 200 }
// ];
this.list = Object.freeze([
{ value: 300 },
{ value: 200 }
]);
}
}
}
</script>
<style scoped>
</style>
性能提升效果对比
在基于 Vue 的一个 很大的表格 里,可以看到在渲染一个一个 1000 x 10 的表格的时候,开启Object.freeze()
前后重新渲染的对比。
开启优化之前
开启优化之后
为什么Object.freeze()
的性能会更好
不使用Object.freeze()
的CPU开销
使用 Object.freeze()
的CPU开销
对比可以看出,使用了 Object.freeze()
之后,减少了 observer 的开销
通过组件懒加载优化超长应用内容初始渲染性能
例如在一个复杂的应用的主界面中,整个主界面由非常多不同的模块组成,而用户看到的往往只有首屏一两个模块。在初始渲染的时候不可见区域的模块也会执行和渲染,带来一些额外的性能开销
使用组件懒加载在不可见时只需要渲染一个骨架屏,不需要真正渲染组件
你可以对组件直接进行懒加载,对于不可见区域的组件内容,直接不进行加载和初始化,避免初始化渲染运行时的开销。
详情查看这篇文章:vue组件懒加载(Vue Lazy Component )
优化无限列表性能
如果你的应用存在非常长或者无限滚动的列表,那么采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。
vue-virtual-scroll-list 和 vue-virtual-scroller 都是解决这类问题的开源项目。你也可以参考 Google 工程师的文章Complexities of an Infinite Scroller 来尝试自己实现一个虚拟的滚动列表来优化性能,主要使用到的技术是 DOM 回收、墓碑元素和滚动锚定。
Google 工程师绘制的无限列表设计
扁平化 Store 数据结构
很多时候,我们会发现接口返回的信息是如下的深层嵌套的树形结构
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
假如直接把这样的结构存储在 store 中,如果想修改某个 commenter 的信息,我们需要一层层去遍历找到这个用户的信息,同时有可能这个用户的信息出现了多次,还需要把其他地方的用户信息也进行修改,每次遍历的过程会带来额外的性能开销。
假设我们把用户信息在 store 内统一存放成 users[id]
这样的结构,修改和读取用户信息的成本就变得非常低。
你可以手动去把接口里的信息通过类似数据的表一样像这样存起来,也可以借助一些工具,这里就需要提到一个概念叫做 JSON数据规范化(normalize)
, Normalizr 是一个开源的工具,可以将上面的深层嵌套的 JSON 对象通过定义好的 schema 转变成使用 id 作为字典的实体表示的对象。
举个例子,针对上面的 JSON 数据,我们定义 users
comments
articles
三种 schema:
import {normalize, schema} from 'normalizr';
// 定义 users schema
const user = new schema.Entity('users');
// 定义 comments schema
const comment = new schema.Entity('comments', {
commenter: user,
});
// 定义 articles schema
const article = new schema.Entity('articles', {
author: user,
comments: [comment],
});
const normalizedData = normalize(originalData, article);
normalize 之后就可以得到下面的数据,我们可以按照这种形式存放在 store 中,之后想修改和读取某个 id 的用户信息就变得非常高效了,时间复杂度降低到了 O(1)。
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
需要了解更多请参考 normalizr 的文档 https://github.com/paularmstrong/normalizr
避免持久化 Store 数据带来的性能问题