浅谈Vue 初始化性能优化

前言

一般来说,你不需要太关心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);
  }
});

这个示例写起来略显啰嗦,但它已经实现了我们想要的顺序渲染的效果。页面会在A组件初始化完后显示,然后再按顺序渲染其余的组件,整个页面渲染方式看起来是流式的。

有些人可能会担心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...

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

(0)

相关推荐

  • 浅谈Vue 初始化性能优化

    前言 一般来说,你不需要太关心vue的运行时性能,它在运行时非常快,但付出的代价是初始化时相对较慢.在最近开发的一个Hybrid APP里,Android Webview初始化一个较重的vue页面竟然用了1200ms ~ 1400ms,这让我开始重视vue的初始化性能,并最终优化到200 ~ 300ms,这篇文章分享我的优化思路. 性能瓶颈在哪里? 先看一下常见的vue写法:在html里放一个app组件,app组件里又引用了其他的子组件,形成一棵以app为根节点的组件树. <body> <

  • 浅谈一下Nginx性能优化

    目录 Nginx 性能优化 1.Nginx运行工作进程数量 2.Nginx运行CPU亲和力 3.Nginx最大打开文件数 4.Nginx事件处理模型 5.开启高效传输模式 6.连接超时时间 7.fastcgi 调优 8.gzip 调优 9.expires 缓存调优 10.防盗链 11.内核参数优化 12.关于系统连接数的优化 Nginx 性能优化 1.Nginx运行工作进程数量 Nginx运行工作进程个数一般设置CPU的核心或者核心数x2.如果不了解cpu的核数,可以top命令之后按1看出来,也

  • 浅谈webpack 构建性能优化策略小结

    背景 如今前端工程化的概念早已经深入人心,选择一款合适的编译和资源管理工具已经成为了所有前端工程中的标配,而在诸多的构建工具中,webpack以其丰富的功能和灵活的配置而深受业内吹捧,逐步取代了grunt和gulp成为大多数前端工程实践中的首选,React,Vue,Angular等诸多知名项目也都相继选用其作为官方构建工具,极受业内追捧.但是,随者工程开发的复杂程度和代码规模不断地增加,webpack暴露出来的各种性能问题也愈发明显,极大的影响着开发过程中的体验. 问题归纳 历经了多个web项目

  • 浅谈vue项目打包优化策略

    使用vue-cli部署生产包时,发现资源包很大,打包后的vendor.js达到了1.4M,这已经很大了,而且会影响到首屏加载.那么,怎么优化呢? 1.组件按需加载 这是首先可以优化的点.如果频繁使用了第三方组件/UI库,如我的项目中经常同时使用了 element-ui, mint-ui,echarts等组件库,如果全部引入,项目体积非常大,这时可以按需引入组件. 示例如下: 1.1 element-ui 首先,安装 babel-plugin-component: npm install babe

  • 浅谈vue加载优化策略

    vue.js是一个比较流行的前端框架,与react.js.angular.js相比来说,vue.js入手曲线更加流畅,不管掌握多少都可以快速上手.但是单页面应用也都有其弊病,有时候首屏加载慢的让人捏舌.今天我们以vue cli3.x来说一说如何行之有效的缓解此问题! 方法一 路由懒加载 首屏加载慢的原因无非就是单页面应用需要加载完整个路由表上的页面,而路由懒加载就是来解决这个问题的.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了.下面这个

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • 浅谈Vue.js之初始化el以及数据的绑定说明

    1.初始化el 2.数据绑定说明 3.监听值的变化 以上这篇浅谈Vue.js之初始化el以及数据的绑定说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 浅谈Vue.js 1.x 和 2.x 实例的生命周期

    在Vue.js中,在实例化Vue之前,它们都是以HTML的文本形式存在文本编辑器中.当实例化后将经历创建.编译.销毁三个主要阶段. 以下是Vue.js 1.x 实例的生命周期图示: Vue.js 1.x 的生命周期钩子 1. init 在实例开始初始化时同步调用.此时数据观测.事件和Watcher都尚未初始化. 2. created 在实例化创建之后同步调用.此时实例已经结束解析选项,已建立:数据绑定.计算属性.方法.Watcher/事件回调,但是还没有开始DOM编译,$el还不存在. 3. b

  • 浅谈VUE监听窗口变化事件的问题

    Vuejs 本身就是一个 MVVM 的框架.但是在监听 window 上的 事件 时,往往会显得 力不从心. 比如 这次是 window.resize恩,我做之前也是百度了一下.看到大家伙都为这个问题而发愁. 问题: 今天我也 遇到了这样一个问题, 是关于canvas 自适应. 根据窗口的变化去变化 canvas 的宽度备注: 重要的问题说三遍 解决 框架内的bug 先说 框架 版本 版本 版本 (这里用的 Vue 2.x .ES6) 解决方案: 第一步: 先在 data 中去 定义 一个记录宽

  • 浅谈vue单一组件下动态修改数据时的全部重渲染

    今天在学习vue的过程中,发现一个有趣的现象. 在某一组件下的某一数据通过点击事件被动态修改的时候,对应view中的数据同步的进行了修改,没错,这不是废话吗,vue的一大特色就是数据的双向绑定.可有趣的是,该组件下我写的另一个用Math.random()的data值对应的值和视图也发生了变化 这就让我这个刚入门的小白有点奇怪了,我修改一个,怎么变了两个????脑洞放开一想,会不会数据在双向同步的时候,发生了什么,比如.是不是只要有一个节点变了,node都重新进行了加载??? 我想这其中的缘由必定

随机推荐