基於VUE的SPA單頁應用開發-加載性能篇

一、基於異步數據的vue頁面刷新

先看看基於異步數據的vue頁面刷新後,都發生了啥~css

如圖所示:
html

圖1 基於異步數據的vue頁面刷新 網絡請求圖vue

步驟以下:ajax

  • step1:請求頁面;
  • step2:請求頁面內的css、js資源;
  • step3:vue頁面初始化;
  • step4:頁面渲染,框架呈現[無數據內容];
  • step5:請求頁面實際數據;
  • step6:數據ready,填充視圖,圖片資源加載;
  • step7:完整頁面呈現。

步驟分析:vuex

  • step1:請求html文件;promise

  • step2:請求資源;
    優化點:
    • a、屢次訪問的資源緩存:可從MD五、組件打包方式等角度再細分;
    • b、app框架資源預加載:若是是hybird開發的app,可經過app框架預加載的方式,將單頁應用的資源提早緩存。
      單頁的css、js資源,與傳統頁面的資源相比,規模要大不少。其集合了幾乎單頁應用的全部css、js文件,隨着應用的規模大小成正比增加。合理的緩存處理,將大大提高頁面加載速度。a、b兩點可實現性能加速的緣由是,本地加載過的資源,會緩存在本地;頁面請求資源時,瀏覽器會先查找緩存,若是有緩存,則本地取,節省了網絡請求。[可瞭解瀏覽器的強緩、弱緩]
  • step3:頁面初始化;
    優化點:
    • 利用v-if指令按需加載組件~
      因爲vue在初始化過程當中,會深度查找子組件,生成依賴,構建虛擬DOM,因此其初始化時間相對較長;不過在查找過程當中,遇到v-if爲false的組件,將中止深度查找,從而節省初始化時間。由此可經過控制v-if的布爾值,實現性能優化和組件的按需加載。例如在初始化過程當中,僅首屏必須的組件v-if=true,其餘組件[如非首屏組件、彈窗組件等],可在下一事件循環中[經過setTimeout(fn, 0)等異步操做可實現],或者首屏資源加載完成後,開啓。
  • step4:默認數據頁面渲染;(默認數據由vuex提供)
    優化點:無數據的頁面框架的渲染展示[一般所說的灰框],讓用戶提早感知頁面,從而提高用戶體驗;(包括讀取vuex數據,進行渲染)瀏覽器

  • step5:異步請求數據,與step4同時進行,經過ajax實現;緩存

  • step6:資源加載;
    優化點:進行圖片[視頻]分批加載優化,從而增長同一帶寬下單圖的加載速度,加速首屏展示。
    建議圖片加載流程:
    • step6-1:首屏展示必須圖片加載及首屏默認圖片;[如:banner圖第一張];
    • step6-2:首屏其他圖片加載及其餘默認圖片;[如:剩餘的banner圖];
    • step6-3:非首屏 or 彈窗。[接下來的加載順序,可根據需求調整。但要遵循一條原則,不影響首屏的用戶交互。]

二、基於異步數據的vue頁面的路由跳轉

再來看看經過單頁路由跳轉到新頁,又發生了什麼?
性能優化

圖2 基於異步數據的vue頁面路由跳轉 網絡請求圖網絡

頁面加載步驟:

  • step1:捕獲到路由變化;
  • step2:初始化該頁面,並默認數據渲染;(包括讀取vuex數據,進行渲染)[step三、step4]
  • step3:異步請求最新的初始化數據;[同step5]
  • step4:資源分批預加載。[同step6]

總結:

  • 優點:頁內跳轉性能很是贊。對比圖1和圖2,在路由內跳轉時減小了圖1中step一、step2的頁面請求和.css、.js的請求時間[節省1s+],頁面展示嗖嗖的。再好好結合vuex的數據流,能夠給用戶很是棒的體驗。

  • 劣勢:再觀察圖1的網絡請求圖,能夠發現如下幾點:
    • a、css、js資源相對傳統頁面,量更大,加載時間加長;
    • b、vue的首屏展示,依賴異步數據的請求,相對傳統同步頁面,增長了單獨的數據請求時間消耗;
    • c、頁面渲染,在js執行完畢以後,纔開始進行;而最終的首屏展示,則須要等待異步數據請求到達以後。
      因爲a、b的存在,c的首屏展示時間相對傳統頁面更慢。

VUE的異步單頁應用優點與劣勢很是明顯,缺點是初始化時間長,依賴js資源的加載;優點是運行速度快,路由內跳轉幾乎沒太多的時間消耗。若是是必定規模大小的單頁應用,它將是不錯的選擇。特別是使用hybird開發,經過app框架將資源預加載以後,需依賴js資源的劣勢也必將不存在,那將給到用戶傳統頁面沒法給到的體驗。

Q:那有什麼辦法來解決這些劣勢嗎?
A:在接下來的3中,將提出一種解決方案。

三、提速方案

對於a點,資源量大,能夠從打包方式、緩存、CDN分發等角度進行處理;
對於b點,有兩種方式解決:

  • 一、同步+異步數據請求:刷新頁面時,使用同步MVC框架的方式,經過後臺路由帶入初始化數據;頁內路由跳轉時,仍然採用異步的方式進行。
  • 二、異步數據請求提早:刷新頁面時,將數據請求提早至js資源加載前,因爲網絡請求可併發多個,將節省單獨的數據請求時間。

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的節點或者葉子節點,中止查找。從示例的組件結構圖,咱們能夠看出,
初始化中組件查找過程爲:

  • step1:首先查找根節點的子組件nav組件,其爲葉子節點,編譯,返回;
  • step2:查找app的第二個子組件(節點)content,其非葉子節點,且無v-if標記,繼續深度查找;查找其子節點c-a組件,爲葉子節點,編譯,返回content;查找另外一個子節點c-b組件,葉子節點,編譯,返回;
  • step3:節點content查找完畢;返回app,查找sidebar節點,sidebar非葉子節點,且無v-if,繼續深度查找;同step2,最終返回app節點。
  • step4:全部組件編譯完畢,初始化完畢,渲染。
    如上示例,將深度遍歷全部子組件,再完成渲染。若是將首屏不須要展示的組件設置成v-if,將下降深度查找的複雜度,從而加速組件初始化,加速頁面的呈現。

加速代碼以下:

<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,組件的編譯查找過程以下:

  • step1:不變;
  • step2:查找content組件,其爲v-if=false,中止,並返回根節點;
  • step3:查找sidebar組件,其爲v-if=false,中止,並返回根節點;
  • step4:全部組件編譯完畢,初始化完畢,渲染。

是否是快了不少~~由此,首先被渲染,出現的是nav組件結構;另外兩個組件經過showContent,showSidebar控制。

爲什麼這裏使用setTimeout(fn, 0)控制兩個組件的狀態變化呢?

由於setTimeout是時間異步處理模塊,經過其設置,相應的處理方法將在下一事件循環中才被執行,而VUE的渲染時機爲本次主線程執行完畢。如此,使得另外兩個組件的編譯推遲到首次渲染以後,從而實現組件加速。

tips:這裏須要注意,不是將組件設置v-if=false就能夠了,要看看v-if=true的開啓時機,若是是同一事件循環中被開啓,便沒有意義了。由於vue的數據驅動渲染時機,是同一事件循環中的代碼所有執行完畢以後,拿到數據的最終狀態才進行。同時,setTimeout(fn, 0)也不可亂用。

附加:圖片分批預加載的js腳本

參數說明:
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實例,經過必定的條件,觸發圖片預加載,將其進行分散,從而達到性能加速的同時,用戶交互體驗也不受影響。

相關文章
相關標籤/搜索