先看看基于异步数据的vue页面刷新后,都发生了啥~css
如图所示:
html
图1 基于异步数据的vue页面刷新 网络请求图vue
步骤以下:ajax
步骤分析:vuex
step1:请求html文件;promise
step4:默认数据页面渲染;(默认数据由vuex提供)
优化点:无数据的页面框架的渲染展示[一般所说的灰框],让用户提早感知页面,从而提高用户体验;(包括读取vuex数据,进行渲染)浏览器
step5:异步请求数据,与step4同时进行,经过ajax实现;缓存
再来看看经过单页路由跳转到新页,又发生了什么?
性能优化
图2 基于异步数据的vue页面路由跳转 网络请求图网络
页面加载步骤:
总结:
优点:页内跳转性能很是赞。对比图1和图2,在路由内跳转时减小了图1中step一、step2的页面请求和.css、.js的请求时间[节省1s+],页面展示嗖嗖的。再好好结合vuex的数据流,能够给用户很是棒的体验。
VUE的异步单页应用优点与劣势很是明显,缺点是初始化时间长,依赖js资源的加载;优点是运行速度快,路由内跳转几乎没太多的时间消耗。若是是必定规模大小的单页应用,它将是不错的选择。特别是使用hybird开发,经过app框架将资源预加载以后,需依赖js资源的劣势也必将不存在,那将给到用户传统页面没法给到的体验。
Q:那有什么办法来解决这些劣势吗?
A:在接下来的3中,将提出一种解决方案。
对于a点,资源量大,能够从打包方式、缓存、CDN分发等角度进行处理;
对于b点,有两种方式解决:
tips:若是不是mvvm的异步单页,推荐使用同步+异步的方式,页面的展示能够提早至js资源加载以前。[因为mvvm框架下的页面视图经过数据进行驱动,该驱动的基本须要依赖js脚本实现,因此必须等待js加载完毕,才能正确展示页面。所以,在mvvm框架下,同步+异步的方式仅能节省数据请求时间,但其余单页应用能够节省数据请求时间+js资源加载时间]
ps:js的加载顺序:不影响页面初始化呈现的js底部后置:如日志、分享、im的相关js。
以下示例:
<app> <app-nav></app-nav> <app-content></app-content> <app-sidebar></app-sidebar> </app>
其页面结构与组件结构关系图以下:
vue初始化的组件编译原则是,按照深度查找,遇到v-if为false的节点或者叶子节点,中止查找。从示例的组件结构图,咱们能够看出,
初始化中组件查找过程为:
加速代码以下:
<app> <app-nav></app-nav> <app-content v-if="showContent"></app-content> <app-sidebar v-if="showSidebar"></app-sidebar> </app> new Vue({ data: { showContent: false, showSidebar: false }, created () { // 显示content setTimeout(() => { this.showContent = true; }, 0); // 显示sidebar setTimeout(() => { this.showSidebar = true; }, 0); } });
以上代码将组件content与sidebar的v-if设置成false,组件的编译查找过程以下:
是否是快了不少~~由此,首先被渲染,出现的是nav组件结构;另外两个组件经过showContent,showSidebar控制。
为什么这里使用setTimeout(fn, 0)控制两个组件的状态变化呢?
由于setTimeout是时间异步处理模块,经过其设置,相应的处理方法将在下一事件循环中才被执行,而VUE的渲染时机为本次主线程执行完毕。如此,使得另外两个组件的编译推迟到首次渲染以后,从而实现组件加速。
tips:这里须要注意,不是将组件设置v-if=false就能够了,要看看v-if=true的开启时机,若是是同一事件循环中被开启,便没有意义了。由于vue的数据驱动渲染时机,是同一事件循环中的代码所有执行完毕以后,拿到数据的最终状态才进行。同时,setTimeout(fn, 0)也不可乱用。
参数说明:
auto: 是否自执行
imgs: 需预加载的图片列表,为二维表
ignore:在自执行过程当中,须要跳过的图片批次脚标
firstSetReady: 第一组图片完成加载之后,置为true,便于外部掌握状态[通常首屏资源为第一组图片]
finished: 全部图片资源加载完毕
说明:
非自执行的需求,可直接调用loadOneSetImages方法,返回值为promise
let co = require('co') class Preload { // 定义构造函数的数据结构,建立实例对象时,自动初始化 constructor(auto, imgs = [], ignore = []) { this.imgs = imgs; this.ignore = ignore; this.auto = auto; this.firstSetReady = false; this.finished = false; this.init(); } // 初始化函数 init() { let me = this // 若是自动执行,则调用co模块,自动加载资源 if (this.auto) { // generator的自执行函数,资源加载完毕时,参数finished置为true co(this.autoExeImageStream.call(this)).then(function() { console.log('资源加载完毕~') me.finished = true }).catch(function() { console.log('资源加载出错~') me.finished = true }) } } // 同步加载分批图片资源,使用generator函数,完成当前批次加载,再启动下一批次的加载 * autoExeImageStream() { let rstList = [] for (let i = 0; i < this.imgs.length; i++) { if (this.ignore.indexOf(i) == -1) { yield this.loadOneSetImages(this.imgs[i], i) } } } // 加载一个批次的图片资源,当前批次全部图片加载完毕后,将firstSetReady置为true;表示第一组图片加载完毕 loadOneSetImages(imgList, i) { let promiseList = [] let me = this imgList.forEach(function(item, index) { promiseList.push(me.loadSingleImage(item)) }) return Promise.all([...promiseList]).then(function() { me.firstSetReady = true }).catch(function() { me.firstSetReady = true }) } // 加载图片资源的函数,每一个图片单独设置primise loadSingleImage(src) { if (!src) { return new Promise(function(resolve, reject) { resolve('noImage') }) } let newImg = new Image() newImg.src = src return new Promise(function(resolve, reject) { newImg.onload = function() { this.total++; resolve('success') } newImg.onerror = function() { reject('fail') } }) } } export default Preload; 调用:this.preloadObj = new Preload(true, imgs)
图片分批预加载脚本经过ES6 promise结合generator函数实现,使得图片按照咱们想要的方式顺序加载,且单批次速度更快。但不能滥用,须要留意图片资源的加载规模与用户交互操做之间的关系。因为,ES6 的 promise在事件循环中的消息处理级别高于DOM事件[or 网络请求等其余异步模块],当有 promise 消息要处理时,其余事件消息将等待,直到promise处理完毕。所以在使用其进行预加载时,必定要结合业务状况及预加载的需求进行设置。若有须要能够建立多个preload实例,经过必定的条件,触发图片预加载,将其进行分散,从而达到性能加速的同时,用户交互体验也不受影响。