浅谈Vue 初始化性能优化


Posted in Javascript onAugust 31, 2017

前言

一般来说,你不需要太关心vue的运行时性能,它在运行时非常快,但付出的代价是初始化时相对较慢。在最近开发的一个Hybrid APP里,Android Webview初始化一个较重的vue页面竟然用了1200ms ~ 1400ms,这让我开始重视vue的初始化性能,并最终优化到200 ~ 300ms,这篇文章分享我的优化思路。

性能瓶颈在哪里?

先看一下常见的vue写法:在html里放一个app组件,app组件里又引用了其他的子组件,形成一棵以app为根节点的组件树。

<body>
  <app></app> 
</body>

而正是这种做法引发了性能问题,要初始化一个父组件,必然需要先初始化它的子组件,而子组件又有它自己的子组件。那么要初始化根标签<app>,就需要从底层开始冒泡,将页面所有组件都初始化完。所以我们的页面会在所有组件都初始化完才开始显示。

这个结果显然不是我们要的,更好的结果是页面可以从上到下按顺序流式渲染,这样可能总体时间增长了,但首屏时间缩减,在用户看来,页面打开速度就更快了。

要实现这种渲染模式,我总结了下有3种方式实现。第3种方式是我认为最合适的,也是我在项目中实际使用的优化方法。

第一种:不使用根组件

这种方式非常简单,例如:

<body>
  <A></A>
  <B></B>
  <C></C>
</body>

抛弃了根组件<app>,从而使A、B、C每一个组件初始化完都立刻展示。但根组件在SPA里是非常必要的,所以这种方式只适用小型页面。

第二种:异步组件

异步组件在官方文档已有说明,使用非常简单:

<app>
  <A></A>
  <B></B>
</app>
new Vue({
  components: {
    A: { /*component-config*/ },
    B (resolve) {
      setTimeout(() => {
        resolve({ /*component-config*/ })
      }, 0);
    }
  }
})

这里<B>组件是一个异步组件,会等到手动调用resolve函数时才开始初始化,而父组件<app>也不必等待<B>先初始化完。

我们利用setTimeout(fn, 0)将<B>的初始化放在队列最后,结果就是页面会在<A>初始化完后立刻显示,然后再显示<B>。如果你的页面有几十个组件,那么把非首屏的组件全设成异步组件,页面显示速度会有明显的提升。

你可以封装一个简单的函数来简化这个过程:

function deferLoad (component, time = 0) {
  return (resolve) => {
    window.setTimeout(() => resolve(component), time)
  };
}

new Vue({
  components: {
    B: deferLoad( /*component-config*/ ),
    // 100ms后渲染
    C: deferLoad( /*component-config*/, 100 )
  }
})

看起来很美好,但这种方式也有问题,考虑下这样的结构:

<app>
  <title></title>
  <A></A>
  <title></title>
  <B></B>
  <title></title>
  <C></C>
</app>

还是按照上面的异步组件做法,这时候就需要考虑把哪些组件设成异步的了。如果把A、B、C都设成异步的,那结果就是3个<title>会首先渲染出来,页面渲染的过程在用户看来非常奇怪,并不是预期中的从上到下顺序渲染。

第三种:v-if 和 terminal指令

这是我推荐的一种做法,简单有效。还是那个结构,我们给要延迟渲染的组件加上v-if:

<app>
  <A></A>
  <B v-if="showB"></B>
  <C v-if="showC"></C>
</app>
new Vue({
  data: {
    showB: false,
    showC: false
  },
  created () {
    // 显示B
    setTimeout(() => {
      this.showB = true;
    }, 0);
    // 显示C
    setTimeout(() => {
      this.showC = true;
    }, 0);
  }
});

这个示例写起来略显??拢???丫?迪至宋颐窍胍?乃承蜾秩镜男Ч?R趁婊嵩?组件初始化完后显示,然后再按顺序渲染其余的组件,整个页面渲染方式看起来是流式的。

有些人可能会担心v-if存在一个编译/卸载过程,会有性能影响。但这里并不需要担心,因为v-if是惰性的,只有当第一次值为true时才会开始初始化。

这种写法看起来很麻烦,如果我们能实现一个类似v-if的组件,然后直接指定多少秒后渲染,那就更好了,例如:

<app>
  <A></A>
  <B v-lazy="0"></B>
  <C v-lazy="100"></C>
</app>

一个简单的指令即可,不需要js端任何配合,并且可以用在普通dom上面,Nice!

在vue里,类似v-if和v-for这种是terminal指令,会在指令内部编译组件。如果你想要自己实现一个terminal指令,需要加上terminal: true,例如:

Vue.directive('lazy', {
  terminal: true,
  bind () {},
  update () {},
  unbind () {}
});

这是vue在1.0.19+新增的功能,由于比较冷门,文档也没有特别详细的叙述,最好的方式是参照着v-if和v-for的源码来写。

我已经为此封装了一个terminal指令,你可以直接使用:https://github.com/Coffcer/vu... 

其他的优化点

除了组件上的优化,我们还可以对vue的依赖改造入手。初始化时,vue会对data做getter、setter改造,在现代浏览器里,这个过程实际上挺快的,但仍然有优化空间。

Object.freeze()是ES5新增的API,用来冻结一个对象,禁止对象被修改。vue 1.0.18+以后,不会对已冻结的data做getter、setter转换。

如果你确保某个data不需要跟踪依赖,可以使用Object.freeze将其冻结。但请注意,被冻结的是对象的值,你仍然可以将引用整个替换调。看下面例子:

<p v-for="item in list">{{ item.value }}</p>
new Vue({
  data: {
    // vue不会对list里的object做getter、setter绑定
    list: Object.freeze([
      { value: 1 },
      { value: 2 }
    ])
  },
  created () {
    // 界面不会有响应
    this.list[0].value = 100;

    // 下面两种做法,界面都会响应
    this.list = [
      { value: 100 },
      { value: 200 }
    ];
    this.list = Object.freeze([
      { value: 100 },
      { value: 200 }
    ]);
  }
})

后记

vue 1.0+ 的组件其实不算轻量,初始化一个组件包括依赖收集、转换等过程,但其实有些是可以放在编译时提前完成的。vue 2.0+ 已经在这方面做了不少的改进:分离了编译时和运行时、提供函数组件等,可以预见,vue 2.0的性能将有很大的提升。

v-lazy-component: https://github.com/Coffcer/vu... 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JAVASCRIPT style 中visibility和display之间的区别
Jan 22 Javascript
JSON.parse 解析字符串出错的解决方法
Jul 08 Javascript
jquery中:input和input的区别分析
Jul 13 Javascript
jQuery实现在列表的首行添加数据
May 19 Javascript
jQuery的文档处理程序详解
May 10 Javascript
BootStrap.css 在手机端滑动时右侧出现空白的原因及解决办法
Jun 07 Javascript
基于javascript的Form表单验证
Dec 29 Javascript
ionic2 tabs使用 Modal底部tab弹出框
Dec 30 Javascript
一文让你彻底搞清楚javascript中的require、import与export
Sep 24 Javascript
Vue.js表单标签中的单选按钮、复选按钮和下拉列表的取值问题
Nov 22 Javascript
在AngularJs中设置请求头信息(headers)的方法及不同方法的比较
Sep 04 Javascript
JS实现的贪吃蛇游戏完整实例
Jan 18 Javascript
Vue keep-alive实践总结(推荐)
Aug 31 #Javascript
深入理解vue-router之keep-alive
Aug 31 #Javascript
vue-router 导航钩子的具体使用方法
Aug 31 #Javascript
浅谈express 中间件机制及实现原理
Aug 31 #Javascript
JavaScript 通过Ajax 动态加载CheckBox复选框
Aug 31 #Javascript
BootStrap下的弹出框加载select2框架失败的解决方法
Aug 31 #Javascript
Angular2 http jsonp的实例详解
Aug 31 #Javascript
You might like
多文件上传的例子
2006/10/09 PHP
特转载一高手总结PHP学习资源和链接.
2006/12/05 PHP
PHP学习笔记之二
2011/01/17 PHP
php的urlencode()URL编码函数浅析
2011/08/09 PHP
一个PHP的远程图片抓取函数分享
2013/09/25 PHP
php将csv文件导入到mysql数据库的方法
2014/12/24 PHP
mysql_escape_string()函数用法分析
2016/04/25 PHP
Javascript里使用Dom操作Xml
2006/09/20 Javascript
某人初学javascript的时候写的学习笔记
2010/12/30 Javascript
使用JavaScript 实现对象 匀速/变速运动的方法
2013/05/08 Javascript
jquery 定位input元素的几种方法小结
2013/07/28 Javascript
JSON中双引号的轮回使用过程中一定要小心
2014/03/05 Javascript
JavaScript函数学习总结以及相关的编程习惯指南
2015/11/16 Javascript
9个让JavaScript调试更简单的Console命令
2016/11/14 Javascript
理解AngularJs篇:30分钟快速掌握AngularJs
2016/12/23 Javascript
JavaScript实现提交模式窗口后刷新父窗口数据的方法
2017/06/16 Javascript
vue中计算属性(computed)、methods和watched之间的区别
2017/07/27 Javascript
Vue中import from的来源及省略后缀与加载文件夹问题
2020/02/09 Javascript
解决antd的Form组件setFieldsValue的警告问题
2020/10/29 Javascript
[01:08:17]2018DOTA2亚洲邀请赛3月29日 小组赛B组 EG VS VGJ.T
2018/03/30 DOTA
python 字符串转列表 list 出现\ufeff的解决方法
2017/06/22 Python
Python使用plotly绘制数据图表的方法
2017/07/18 Python
Python基于jieba库进行简单分词及词云功能实现方法
2018/06/16 Python
python return逻辑判断表达式实现解析
2019/12/02 Python
Python Gluon参数和模块命名操作教程
2019/12/18 Python
python爬虫中url管理器去重操作实例
2020/11/30 Python
英国在线购买轮胎、预订汽车、汽车维修和装配网站:Protyre
2020/04/12 全球购物
Vrbo英国:预订度假屋
2020/08/19 全球购物
德国BA保镖药房中文网:Bodyguard Apotheke
2021/03/09 全球购物
接口可以包含哪些成员
2012/09/30 面试题
秋游活动策划方案
2014/02/16 职场文书
学校三八妇女节活动情况总结
2014/03/09 职场文书
销售业务员岗位职责
2015/02/13 职场文书
2015年银行员工工作总结
2015/04/24 职场文书
MySql新手入门的基本操作汇总
2021/05/13 MySQL
JavaScript 定时器详情
2021/11/11 Javascript