大數據量場景下的Vue性能優化

性能優化最常見的落腳點是在網絡和dom上,但是在大數據量的場景下,由於Vue本身的特性,可能會造成js運行層面的性能問題,這篇文章討論的就是針對這一部分的性能優化方案。

模擬一個大數據量的場景

// App.vue
<template>
    <div>
        <p>It's {{ firstUser.name }}'s show time</p>
        <div>total: {{ total }}</div>
    </div>
</template>

<script>
const user = []
let i = 0
while (i++ < 50000) {
    user.push({
        id: i,
        age: 18,
        name: `kunkun_${i}`,
        alais: 'Irving',
        gender: 'female',
        education: 'senior high school',
        height: 'xxx',
        weight: 'xxx',
        hobby: 'xxx',
        tag: 'xxx',
        skill: {
            sing: 0,
            dance: 0,
            rap: 0,
            basketball: 100,
        },
    })
}

export default {
  data: {
    userList: user,
  },

  computed: {
    firstUser() {
      const userList = this.userList
      return userList.length ? userList[0] : {}
    },

    total() {
      return this.userList.length
    },
  },
}

如以上代碼所示,模擬了5萬個用戶,每一個用戶擁有id, name, age等等字段。 jsfiddle

打開chrome devtool的Performance工具,可以看到,渲染這個組件的過程中,Observer這個階段耗時2.19s(測試機器配置爲桌面端i7,16g內存)。
未優化.png

接下來,我會一步一步的把這一段耗時減少到10ms。

分析原因

衆所周知,Vue在渲染組件的時候,會對data對象進行改造,遍歷data的key,調用defineProperty方法定義它的setter和getter。如果某個字段是Object,或者Array,還會遞歸的對這個字段進行上訴操作。

通常情況下,這個操作耗時是很短的,但是當數據量非常大的時候,對每一個數據項的每一個字段都進行defineProperty的操作就是一個昂貴的操作,所以性能優化的出發點就是減少defineProperty的次數。

Step 1, 減少無用字段

在這個模擬的例子當中,其實我只需要2個字段,一個是name,一個是id(id甚至也可以不要),所以我把多餘的字段都去掉,一共減少了8個String類型的字段,和一個Object類型的字段,可以減少 (8 + 4) * n次defineProperty操作和n次遞歸調用。看看結果如何。
去除無用字段.png

Observer這個操作從2.2s減少到了515ms,提升還是比較大的。

Step2,數據扁平化

在當前版本(2.x)的Vue當中,對於數據變動的檢測有許多限制,比如不能檢測對象屬性的添加和刪除;不能檢測到通過數據索引直接設置數據項等等。

所以,當一個數組的數據項都是基本數據類型的時候,Vue不會進行任何操作。

首先,把user數據拍扁

const user = []
let i = 0
while (i++ < 50000) {
    user.push(`kun_${i}`, i) // 通過index爲基數還是偶數分辨是name還是id
}

然後,相應的改變computed的計算方法,不影響渲染邏輯和業務邏輯

...
computed: {
    firstUser() {
        const userList = this.userList
        return userList.length ? { name: userList[0], id: userList[1] } : {}
    },

    total() {
        return this.userList.length / 2
    },
}
...

jsfiddle - 數據扁平化

看看結果如何
扁平數據.png
從上圖可以看出,結果非常的明顯,從515ms直接減少到了7ms,幾乎完全避免了性能損耗。

Step3,利用computed

到此爲止,性能上的問題已經解決了,但是扁平的數據會影響業務代碼的開發效率和可讀性,同時數據和它的index產生了深耦合,如果我們需要添加一個字段使用或者改變下順序,很容易出問題。
不過,我們可以利用computed計算屬性把已經被拍扁的數據重新組裝起來。由於Vue的響應式數據改造只針對data選項和props選項,不包括computed,所以只會產生很少的函數運行耗時。

export default {
    data() {
        return {
            // 扁平的數據存起來
            originSserList: user,
        }
    },

    computed: {
        firstUser() {
            const userList = this.userList
            return userList.length ? userList[0] : {}
        },

        total() {
            return this.userList.length
        },

        // 重新'組裝'便於使用的計算屬性,不影響原本的渲染和業務邏輯
        userList() {
            const result = []
            const user = this.originSserList
            for(let i = 0; i < user.length; i += 2) {
                const name = user[i]
                const id = user[i + 1]
                result.push({ name, id })
            }
            return result
        },
    },
}

看看這種情況下的Performance。
computed+扁平.png
僅僅只是多出了10ms的函數運行時間。

到這裏,在無需改動任何的渲染邏輯和業務邏輯的情況下,將js的運行時間從2.2s減少到了10ms左右,提升了200倍。並且這些數據是在桌面端i7處理器下得到的,大大超越了絕大部分的用戶的機器性能,更不用說移動端了,所以在實際的大數據量場景下,能取得更加明顯的用戶可感知的性能提升。

jsfiddle - 數據扁平化 + computed

Step4,數據靜態化

沒想到吧,還有Step4?已經沒有優化空間了呀。

在這個模擬的場景裏面,確實沒有優化的空間了,不過,並不是所有的數據都可以很好的進行扁平化處理,這可能涉及到方方面面的原因與權衡。那麼這種情況下,如何進行優化呢?

通常在Vue組件當中,都是把數據放在data選項當中,Vue會對data選項中的數據進行響應式改造,我稱之"動態數據"或者"響應式"數據,但是並不是所有的數據都會發生變化的,很多時候,特別是大數據量場景下的數據是不會或者很少發生變化的,這種情況下,就沒有必要把它放到data選項中去,在beforeCreated當中進行數據初始化,也不會影響數據的使用。

beforeCreated() {
    this.userList = xxx // 記得把data當中的userList刪掉
}

這種處理方式,我稱之爲數據靜態化,這種數據,我稱之爲"靜態數據"。

但是,有一點需要特別的注意,靜態數據並不在Vue的響應式系統當中,也就是說當你進行this.userList = newUserList時,視圖不會重新渲染,對應的computed計算屬性也不會重新計算。沒有了Vue提供的響應式系統,如果數據變動的時候,我們需要手動的去計算對應的數據,可能還需要配合$forceUpdate這個api去重新渲染視圖。此時,需要在性能和代碼可讀性與開發效率上進行取捨與權衡。

總結

由於Vue的響應式系統,大數據量場景下可能會造成js運行層面的性能問題,可以通過3個方法去解決

  • 減少無用字段
  • 數據扁平化
  • 數據靜態化

這3個方法相互並不衝突,可以根據實際情況選擇其中的1種或多種方法進行組合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章