导语 | Vue是一套用于构建用户界面的渐进式的JavaScript框架。它具有体积小,更高的运行效率,双向数据绑定,生态丰富、学习成本低等优点,所以Vue也被广泛用在移动端跨平台框架上。接下来,我将为大家梳理10个实现Vue.js极致性能优化的技巧,以供大家在实际运用中使用。

Vue框架通过数据双向绑定和虚拟DOM技术,帮我们处理了前端开发中最脏最累的DOM操作部分,我们不再需要去考虑如何操作DOM以及如何最高效地操作DOM,但是我们仍然需要去关注Vue在跨平台项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。

一、v-for遍历必须为item添加key,

       且避免同时使用v-if

在列表数据进行遍历渲染时,需要为每一项item设置唯一key值,方便Vue.js内部机制精准找到该条列表数据。当state更新时,新的状态值和旧的状态值对比,较快地定位到diff。

我们在使用的使用经常会使用index(即数组的下标)来作为key,但其实这是不推荐的一种使用方法。

举个例子:

var list = [{id: 1,name: 'test1',},{id: 2,name: 'test2',},{id: 3,name: 'test3',},
]<div v-for="(item, index) in list" :key="index" >{{item.name}}</div>

在最后一条数据后再加一条数据:

var list = [{id: 1,name: 'test1',},{id: 2,name: 'test2',},{id: 3,name: 'test3',},{id: 4,name: '我是在最后添加的一条数据',},
]

此时前三条数据直接复用之前的,新渲染最后一条数据,此时用index作为key,没有任何问题。

在中间插入一条数据:

var list = [{id: 1,name: 'test1',},{id: 4,name: '我是插队的那条数据',},{id: 2,name: 'test2',},{id: 3,name: 'test3',},
]

此时更新渲染数据,通过index定义的key去进行前后数据的对比,发现:

之前的数据                         之后的数据key: 0  index: 0 name: test1     key: 0  index: 0 name: test1key: 1  index: 1 name: test2     key: 1  index: 1 name: 我是插队的那条数据key: 2  index: 2 name: test3     key: 2  index: 2 name: test2key: 3  index: 3 name: test3

通过上面清晰的对比,发现除了第一个数据可以复用之前的之外,另外三条数据都需要重新渲染。

是不是很惊奇,我明明只是插入了一条数据,怎么三条数据都要重新渲染?而我想要的只是新增的那一条数据新渲染出来就行了。

最好的办法是使用数组中不会变化的那一项作为key值,对应到项目中,即每条数据都有一个唯一的id,来标识这条数据的唯一性;使用id作为key值,我们再来对比一下向中间插入一条数据,此时会怎么去渲染。

之前的数据                               之后的数据key: 1  id: 1 index: 0 name: test1     key: 1  id: 1 index: 0  name: test1key: 2  id: 2 index: 1 name: test2     key: 4  id: 4 index: 1  name: 我是插队的那条数据key: 3  id: 3 index: 2 name: test3     key: 2  id: 2 index: 2  name: test2key: 3  id: 3 index: 3  name: test3

现在对比发现只有一条数据变化了,就是id为4的那条数据,因此只要新渲染这一条数据就可以了,其他都是就复用之前的。

总结:所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外Vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让Vue可以区分它们,否则Vue只会替换其内部属性而不会触发过渡效果。

v-for遍历避免同时使用v-if,v-for比v-if优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成computed属性。

二、长列表性能优化

Vue会通过Object.defineProperty对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要Vue来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止Vue劫持我们的数据呢?可以通过Object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {data: () => ({users: {}}),async created() {const users = await axios.get("/api/users");this.users = Object.freeze(users);}
};

三、Vue组件中的data是函数而不是对象

export default {data() {// data是一个函数,data: function() {}的简写return {// 页面要初始化的数据name: 'bartonwang',};},
};

而非如下所示:

export default {data: {// data是一个对象name: 'bartonwang',},
};

当一个组件被定义,data必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例,复用在多个页面。

如果data是一个纯碎的对象,则所有的实例将共享引用同一份data数据对象,无论在哪个组件实例中修改data,都会影响到所有的组件实例。

如果data是函数,每次创建一个新实例后,调用data函数,从而返回初始数据的一个全新副本数据对象。

这样每复用一次组件,会返回一份新的data数据,类似于给每个组件实例创建一个私有的数据空间,让各个组件的实例各自独立,互不影响,保持低耦合。

四、Vue钩子函数之钩子事件hookEvent,

       监听组件简化代码

用法:

  1. 通过$on(eventName, eventHandler) 侦听一个事件。

  2. 通过$once(eventName,eventHandler) 一次性侦听一个事件。

  3. 通过$off(eventName, eventHandler) 停止侦听一个事件。

通常实现一个定时器的调用与销毁我可能会以以下方式实现:

export default{data(){timer:null  // 需要创建实例},mounted(){this.timer = setInterval(()=>{//具体执行内容console.log('1');},1000);}beforeDestory(){clearInterval(this.timer);this.timer = null;}
}

这种方法存在的问题是:

vue实例中需要有这个定时器的实例,感觉有点多余。创建的定时器代码和销毁定时器的代码没有放在一起,不容易维护,通常很容易忘记去清理这个定时器。

使用$on(‘hook:’)监听beforeDestory生命周期可以避免该问题,并且因为只需要监听一次,所以使用$once进行注册监听。

export default{methods:{fn(){const timer = setInterval(()=>{console.log('1');},1000);this.$once('hook:beforeDestory',()=>{ // 监听一次即可clearInterval(timer);timer = null;})}}
}

五、组件懒加载

在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常地大,造成进入首页时需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。

Vue.js 2.0组件级懒加载方案:

  • 支持组件可见或即将可见时懒加载

  • 支持组件延时加载

  • 支持加载真实组件前展示骨架组件,提高用户体验

  • 支持真实组件代码分包异步加载

安装:

npm install@xunlei/vue-lazy-component

在组件中实现局部注册组件:

import { component as VueLazyComponent } from '@xunlei/vue-lazy-component'export default {components: {'vue-lazy-component': VueLazyComponent}
}

需要懒加载的组件将其包裹在vue-lazy-component中,slot值为skeleton指的是在懒加载过程中显示的加载状态组件。

<vue-lazy-component :timeout="5000" tagName="div"><child1 slot="skeleton" /><child2 /><child3 /><child4 /><child5 />
</vue-lazy-component>

六、非响应式数据

初始化时,Vue会对data做getter、setter改造。在Vue的文档中介绍数据绑定和响应时,特意标注了对于经过Object.freeze()方法的对象无法进行更新响应。

性能提升对比

在基于Vue的一个big table benchmark里,可以看到在渲染一个一个1000x10的表格的时候,开启Object.freeze()前后重新渲染的对比。

开启优化之前

开启优化之后

在这个例子里,使用了Object.freeze()比不使用快了4倍。

为什么Object.freeze()的性能会更好,不使用Object.freeze()的CPU开销?

使用Object.freeze()的CPU开销:

对比可以看出,使用了Object.freeze()之后,减少了observer的开销。

七、不要将所有的数据都放到data中

data中的数据都会增加getter和setter,又会收集watcher,这样还占内存。不需要响应式的数据我们可以定义在实例上。

八、v-for元素绑定事件代理

事件代理作用主要是2个:

  1. 将事件处理程序代理到父节点,减少内存占用率。

  2. 动态生成子节点时能自动绑定事件处理程序到父节点。

  • 不使用事件代理,每个span节点绑定一个click事件,并指向同一个事件处理程序:

<div><span v-for="(item,index) of 100000" :key="index" @click="handleClick">{{item}}</span></div>
  • 不使用事件代理,每个span节点绑定一个click事件,并指向不同的事件处理程序

<div><span v-for="(item,index) of 100000" :key="index" @click="function () {}">{{item}}</span></div>
  • 使用事件代理

<div  @click="handleClick"><span v-for="(item,index) of 100000"  :key="index">{{item}}</span></div>

可以看到使用事件代理无论是监听器数量和内存占用率都比前两者要少,同时对比3个图中监听器的数量并没有发现Vue会自动做事件代理,但是一般给v-for绑定事件时,都会让节点指向同一个事件处理程序(第二种情况可以运行,但是eslint会警告),一定程度上比每生成一个节点都绑定一个不同的事件处理程序性能好,但是监听器的数量仍不会变,所以使用事件代理会更好一点。

代码使用:

<ul @click="meths"><li v-for="(item,key) in 10" :key="key" :data-index="key">{{item}}</li></ul>meths(e) {if (e.target.nodeName.toLowerCase() === 'li') {console.log(e.target.innerHTML)console.log(e.target.dataset)}}

九、函数式组件

函数式组件是无状态,它无法实例化,没有任何的生命周期和方法。创建函数式组件也很简单,只需要在模板添加functional声明即可。一般适合只依赖于外部数据的变化而变化的组件,因其轻量,渲染性能也会有所提高。

组件需要的一切都是通过context参数传递。它是一个上下文对象,具体属性查看文档。这里props是一个包含所有绑定属性的对象。

函数式组件

十、函数式组件provide和inject组件通信

痛点:常用的父子组件通信方式都是父组件绑定要传递给子组件的数据,子组件通过props属性接收,一旦组件层级变多时,采用这种方式一级一级传递值非常麻烦,而且代码可读性不高,不便后期维护。

Vue提供了provide和inject帮助我们解决多层次嵌套嵌套通信问题。在provide中指定要传递给子孙组件的数据,子孙组件通过inject注入祖父组件传递过来的数据,可以轻松实现跨级访问父组件的数据。

provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。注意:子孙层的provide会掩盖祖父层provide中相同key的属性值

inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值,from是在可用的注入内容中搜索用的key (字符串或Symbol),意思就是祖父多层provide提供了很多数据,from属性指定取哪一个key;default指定默认值。

从上面这个例子可以看出,只要在父组件中调用了,那么在这个父组件生效的生命周期内,所有的子组件都可以调用inject来注入父组件中的值。

在使用场景中,肯定是希望父组件的数据一旦发生改变,子孙组件获取到的也是父组件更新后的数据。那么,怎么实现父组件与子孙组件所绑定的数据动态响应呢?

-------------------parent.vue----------------------provide(){return {// keyName: {name:this.name}, // value 是对象才能实现响应式,也就是引用类型keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]// keyName: 'test' value 如果是基本类型,就无法实现响应式}},data(){return {name:'张三'}},methods: {changeValue(){this.name = '改变后的名字-李四'}}  -------------grandson.vue-----------------inject:['keyName']create(){console.log(this.keyName) // 改变后的名字-李四
}

 作者简介

王雄

腾讯客户端开发工程师

腾讯客户端开发工程师,目前在IEG增值服务部从事掌上道聚城app开发工作,有丰富的跨平台weex,react-native,flutter开发经验。

 推荐阅读

为什么WebAssembly不是JavaScript的终结者,而是它的“助推器”?

快人一步掌握vue源码解读,搞定diff算法!(超详细)

Linux入门必看:如何在60秒内分析Linux性能?

“Docker VS Kubernetes”是共生还是相爱相杀?


10个技巧!实现Vue.js极致性能优化(建议收藏)相关推荐

  1. Vue.js 应用性能优化,给你专业的分析+解决方案

    目录 介绍 为什么我们需要 Vue JS 性能优化? Vue 性能不佳背后的主要原因 如何检查您的 VueJS 应用的大小? 如何优化 Vue js 应用程序的性能? 1. 在 Vue js 中懒加载 ...

  2. vue 动态路由_Vue.js应用性能优化三

    在上一篇Vue.js应用性能优化二中,我们学习了足够强大的模式,可以显着提高应用程序的性能 - 按照路由分割代码.虽然按照路由拆分代码非常有用,但在用户访问我们的站点后,仍然有很多内部代码不需要.在本 ...

  3. Vue首屏性能优化组件

    Vue首屏性能优化组件 简单实现一个Vue首屏性能优化组件,现代化浏览器提供了很多新接口,在不考虑IE兼容性的情况下,这些接口可以很大程度上减少编写代码的工作量以及做一些性能优化方面的事情,当然为了考 ...

  4. 雅虎35条性能优化建议

    雅虎35条性能优化建议分7类,共35条: [内容]尽量减少HTTP请求数 [服务器]使用CDN(Content Delivery Network) [服务器]添上Expires或者Cache-Cont ...

  5. oracle 优化建议,oracle 性能优化建议小结

    原则一:注意WHERE子句中的连接顺序:ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子 ...

  6. WPF 性能优化建议

    本章讲述:WPF 性能优化建议 20180930 WPF性能优化问题:运行软件发现CPU使用率很大(80%-95%),程序中含有委托,线程,定时器的处理,之前优化时,主要优化线程和定时器相关线程方面的 ...

  7. QML 性能优化建议(二)

    前言 接前一篇文章,QML 性能优化建议(一),这里接着来介绍性能优化建议的第二部分:通用接口元素,在这里会介绍一些常见的元素,如:图片.布局之类的写法. 通用接口元素 图片 图片是任何用户界面的重要 ...

  8. Windows Phone性能优化建议

    Windows Phone性能优化建议 原文:Windows Phone性能优化建议 使用background thread解码图片 在Windows Phone中支持的图片格式有jpg和png,微软 ...

  9. 笔记45 | 代码性能优化建议[转]

    地址 笔记45 | 代码性能优化建议[转] 目录 前言 避免创建不必要的对象 选择Static而不是Virtual 常量声明为Static Final 避免内部的Getters/Setters 使用增 ...

最新文章

  1. Python: translate()审查清理文本字符串
  2. OpenGL之glMatrixMode函数的用法
  3. AIX 访问Linux NFS共享错误案例
  4. Google Chrome —— 使用 PowerShell 命令安装 Google Chrome 浏览器
  5. T6 s1 day19
  6. TCPDUMP/LIBPCAP 3-PCAP 中文手册(1)
  7. [js] 根据元素ID遍历树形结构,查找到所有父元素ID
  8. 【Python学习】 - TensorFlow.keras 不显示epochs进度条的方法
  9. Spring IOC 概述
  10. Python爬取妹子图
  11. 沙盘 服务器未响应,为什么沙盘总是服务启动失败
  12. TSP、MTSP问题遗传算法详细解读及python实现
  13. 网络安全职业_如何开始网络安全职业
  14. 医疗常用信息化系统介绍
  15. HTML+CSS大作业: 抗击疫情网页制作作业_疫情防控网页设计
  16. 洛谷P1273 有线电视网 题解
  17. C语言中的restrict限定符
  18. MySQL基础|设置登录用户权限,访问ip地址---防止数据库误删,详细版
  19. Git安装【Windows环境安装配置】详细教程
  20. 【Python】Python获取当前是否是股市交易日

热门文章

  1. wap精武堂源码php_Showgirl武装秀,“精武堂”大奖等你拿!
  2. 【转】没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))
  3. [Codis] Codis3部署流程
  4. python传入参数的几种方法
  5. python运行pyc文件_Python什么情况下会生成pyc文件?
  6. Git 如何将自己本地分支代码上传到远处主分支
  7. 对于iMazing电脑版需要输入许可证激活编号
  8. 海量数据下,如何使用多线程实现 Excel 导出?附源码!
  9. win10 添加右键 cmd(管理员)
  10. OpenGL视口变换函数:glViewport