溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue.js實(shí)用的性能優(yōu)化技巧分享

發(fā)布時(shí)間:2021-09-09 16:40:58 來(lái)源:億速云 閱讀:106 作者:chen 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容主要講解“Vue.js實(shí)用的性能優(yōu)化技巧分享”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Vue.js實(shí)用的性能優(yōu)化技巧分享”吧!

目錄
  • Functional components

  • Child component splitting

  • Local variables

  • Reuse DOM with v-show

  • KeepAlive

  • Deferred features

  • Time slicing

  • Non-reactive data

  • Virtual scrolling



Functional components

第一個(gè)技巧,函數(shù)式組件,你可以查看這個(gè)在線示例

優(yōu)化前的組件代碼如下:

<template>
  <div class="cell">
    <div v-if="value" class="on"></div>
    <section v-else class="off"></section>
  </div>
</template>

<script>
export default {
  props: ['value'],
}
</script>

優(yōu)化后的組件代碼如下:

<template functional>
  <div class="cell">
    <div v-if="props.value" class="on"></div>
    <section v-else class="off"></section>
  </div>
</template>

然后我們?cè)诟附M件各渲染優(yōu)化前后的組件 800 個(gè),并在每一幀內(nèi)部通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)組件的更新,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化前執(zhí)行 script 的時(shí)間要多于優(yōu)化后的,而我們知道 JS 引擎是單線程的運(yùn)行機(jī)制,JS 線程會(huì)阻塞 UI 線程,所以當(dāng)腳本執(zhí)行時(shí)間過(guò)長(zhǎng),就會(huì)阻塞渲染,導(dǎo)致頁(yè)面卡頓。而優(yōu)化后的 script 執(zhí)行時(shí)間短,所以它的性能更好。

那么,為什么用函數(shù)式組件 JS 的執(zhí)行時(shí)間就變短了呢?這要從函數(shù)式組件的實(shí)現(xiàn)原理說(shuō)起了,你可以把它理解成一個(gè)函數(shù),它可以根據(jù)你傳遞的上下文數(shù)據(jù)渲染生成一片 DOM。

函數(shù)式組件和普通的對(duì)象類型的組件不同,它不會(huì)被看作成一個(gè)真正的組件,我們知道在 patch 過(guò)程中,如果遇到一個(gè)節(jié)點(diǎn)是組件 vnode,會(huì)遞歸執(zhí)行子組件的初始化過(guò)程;而函數(shù)式組件的 render 生成的是普通的 vnode,不會(huì)有遞歸子組件的過(guò)程,因此渲染開(kāi)銷會(huì)低很多。

因此,函數(shù)式組件也不會(huì)有狀態(tài),不會(huì)有響應(yīng)式數(shù)據(jù),生命周期鉤子函數(shù)這些東西。你可以把它當(dāng)成把普通組件模板中的一部分 DOM 剝離出來(lái),通過(guò)函數(shù)的方式渲染出來(lái),是一種在 DOM 層面的復(fù)用。

Child component splitting

第二個(gè)技巧,子組件拆分,你可以查看這個(gè)在線示例。

優(yōu)化前的組件代碼如下:

<template>
  <div :>
    <div>{{ heavy() }}</div>
  </div>
</template>

<script>
export default {
  props: ['number'],
  methods: {
    heavy () {
      const n = 100000
      let result = 0
      for (let i = 0; i < n; i++) {
        result += Math.sqrt(Math.cos(Math.sin(42)))
      }
      return result
    }
  }
}
</script>

優(yōu)化后的組件代碼如下:

<template>
  <div :>
    <ChildComp/>
  </div>
</template>

<script>
export default {
  components: {
    ChildComp: {
      methods: {
        heavy () {
          const n = 100000
          let result = 0
          for (let i = 0; i < n; i++) {
            result += Math.sqrt(Math.cos(Math.sin(42)))
          }
          return result
        },
      },
      render (h) {
        return h('div', this.heavy())
      }
    }
  },
  props: ['number']
}
</script>

然后我們?cè)诟附M件各渲染優(yōu)化前后的組件 300 個(gè),并在每一幀內(nèi)部通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)組件的更新,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script 的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。

那么為什么會(huì)有差異呢,我們來(lái)看優(yōu)化前的組件,示例通過(guò)一個(gè) heavy 函數(shù)模擬了一個(gè)耗時(shí)的任務(wù),且這個(gè)函數(shù)在每次渲染的時(shí)候都會(huì)執(zhí)行一次,所以每次組件的渲染都會(huì)消耗較長(zhǎng)的時(shí)間執(zhí)行 JavaScript。

而優(yōu)化后的方式是把這個(gè)耗時(shí)任務(wù) heavy 函數(shù)的執(zhí)行邏輯用子組件 ChildComp 封裝了,由于 Vue 的更新是組件粒度的,雖然每一幀都通過(guò)數(shù)據(jù)修改導(dǎo)致了父組件的重新渲染,但是 ChildComp 卻不會(huì)重新渲染,因?yàn)樗膬?nèi)部也沒(méi)有任何響應(yīng)式數(shù)據(jù)的變化。所以優(yōu)化后的組件不會(huì)在每次渲染都執(zhí)行耗時(shí)任務(wù),自然執(zhí)行的 JavaScript 時(shí)間就變少了。

不過(guò)針對(duì)這個(gè)優(yōu)化的方式我提出了一些不同的看法,詳情可以點(diǎn)開(kāi)這個(gè) issue,我認(rèn)為這個(gè)場(chǎng)景下的優(yōu)化用計(jì)算屬性要比子組件拆分要好。得益于計(jì)算屬性自身緩存特性,耗時(shí)的邏輯也只會(huì)在第一次渲染的時(shí)候執(zhí)行,而且使用計(jì)算屬性也沒(méi)有額外渲染子組件的開(kāi)銷。

在實(shí)際工作中,使用計(jì)算屬性是優(yōu)化性能的場(chǎng)景會(huì)有很多,畢竟它也體現(xiàn)了一種空間換時(shí)間的優(yōu)化思想。

Local variables

第三個(gè)技巧,局部變量,你可以查看這個(gè)在線示例。

優(yōu)化前的組件代碼如下:

<template>
  <div :>{{ result }}</div>
</template>

<script>
export default {
  props: ['start'],
  computed: {
    base () {
      return 42
    },
    result () {
      let result = this.start
      for (let i = 0; i < 1000; i++) {
        result += Math.sqrt(Math.cos(Math.sin(this.base))) + this.base * this.base + this.base + this.base * 2 + this.base * 3
      }
      return result
    },
  },
}
</script>

優(yōu)化后的組件代碼如下:

<template>
  <div :>{{ result }}</div>
</template>

<script>
export default {
  props: ['start'],
  computed: {
    base () {
      return 42
    },
    result ({ base, start }) {
      let result = start
      for (let i = 0; i < 1000; i++) {
        result += Math.sqrt(Math.cos(Math.sin(base))) + base * base + base + base * 2 + base * 3
      }
      return result
    },
  },
}
</script>

然后我們?cè)诟附M件各渲染優(yōu)化前后的組件 300 個(gè),并在每一幀內(nèi)部通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)組件的更新,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script 的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。

這里主要是優(yōu)化前后組件的計(jì)算屬性 result 的實(shí)現(xiàn)差異,優(yōu)化前的組件多次在計(jì)算過(guò)程中訪問(wèn) this.base,而優(yōu)化后的組件會(huì)在計(jì)算前先用局部變量 base 緩存 this.base,后面則直接訪問(wèn) base變量。

那么為啥這個(gè)差異會(huì)造成性能上的差異呢,原因是你每次訪問(wèn) this.base 的時(shí)候,由于 this.base 是一個(gè)響應(yīng)式對(duì)象,所以會(huì)觸發(fā)它的 getter,進(jìn)而會(huì)執(zhí)行依賴收集相關(guān)邏輯代碼。類似的邏輯執(zhí)行多了,像示例這樣,幾百次循環(huán)更新幾百個(gè)組件,每個(gè)組件觸發(fā) computed 重新計(jì)算,然后又多次執(zhí)行依賴收集相關(guān)邏輯,性能自然就下降了。

從需求上來(lái)說(shuō),this.base 執(zhí)行一次依賴收集就夠了,因此我們只需要把它的 getter 求值結(jié)果返回給局部變量 base,后續(xù)再次訪問(wèn) base 的時(shí)候就不會(huì)觸發(fā) getter,也不會(huì)走依賴收集的邏輯了,性能自然就得到了提升。

這是一個(gè)非常實(shí)用的性能優(yōu)化技巧。因?yàn)楹芏嗳嗽陂_(kāi)發(fā) Vue.js 項(xiàng)目的時(shí)候,每當(dāng)取變量的時(shí)候就習(xí)慣性直接寫 this.xxx 了,因?yàn)榇蟛糠秩瞬⒉粫?huì)注意到訪問(wèn) this.xxx 背后做的事情。在訪問(wèn)次數(shù)不多的時(shí)候,性能問(wèn)題并沒(méi)有凸顯,但是一旦訪問(wèn)次數(shù)變多,比如在一個(gè)大循環(huán)中多次訪問(wèn),類似示例這種場(chǎng)景,就會(huì)產(chǎn)生性能問(wèn)題了。

我之前給 ZoomUI 的 Table 組件做性能優(yōu)化的時(shí)候,在 render table body 的時(shí)候就使用了局部變量的優(yōu)化技巧,并寫了 benchmark 做性能對(duì)比:渲染 1000 * 10 的表格,ZoomUI Table 的更新數(shù)據(jù)重新渲染的性能要比 ElementUI 的 Table 性能提升了近一倍。

Reuse DOM with v-show

第四個(gè)技巧,使用 v-show 復(fù)用 DOM,你可以查看這個(gè)在線示例。

優(yōu)化前的組件代碼如下:

<template functional>
  <div class="cell">
    <div v-if="props.value" class="on">
      <Heavy :n="10000"/>
    </div>
    <section v-else class="off">
      <Heavy :n="10000"/>
    </section>
  </div>
</template>

優(yōu)化后的組件代碼如下:

<template functional>
  <div class="cell">
    <div v-show="props.value" class="on">
      <Heavy :n="10000"/>
    </div>
    <section v-show="!props.value" class="off">
      <Heavy :n="10000"/>
    </section>
  </div>
</template>

然后我們?cè)诟附M件各渲染優(yōu)化前后的組件 200 個(gè),并在每一幀內(nèi)部通過(guò)修改數(shù)據(jù)來(lái)觸發(fā)組件的更新,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script 的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。

優(yōu)化前后的主要區(qū)別是用 v-show 指令替代了 v-if 指令來(lái)替代組件的顯隱,雖然從表現(xiàn)上看,v-showv-if 類似,都是控制組件的顯隱,但內(nèi)部實(shí)現(xiàn)差距還是很大的。

v-if 指令在編譯階段就會(huì)編譯成一個(gè)三元運(yùn)算符,條件渲染,比如優(yōu)化前的組件模板經(jīng)過(guò)編譯后生成如下渲染函數(shù):

function render() {
  with(this) {
    return _c('div', {
      staticClass: "cell"
    }, [(props.value) ? _c('div', {
      staticClass: "on"
    }, [_c('Heavy', {
      attrs: {
        "n": 10000
      }
    })], 1) : _c('section', {
      staticClass: "off"
    }, [_c('Heavy', {
      attrs: {
        "n": 10000
      }
    })], 1)])
  }
}

當(dāng)條件 props.value 的值變化的時(shí)候,會(huì)觸發(fā)對(duì)應(yīng)的組件更新,對(duì)于 v-if 渲染的節(jié)點(diǎn),由于新舊節(jié)點(diǎn) vnode 不一致,在核心 diff 算法比對(duì)過(guò)程中,會(huì)移除舊的 vnode 節(jié)點(diǎn),創(chuàng)建新的 vnode 節(jié)點(diǎn),那么就會(huì)創(chuàng)建新的 Heavy 組件,又會(huì)經(jīng)歷 Heavy 組件自身初始化、渲染 vnodepatch 等過(guò)程。

因此使用 v-if 每次更新組件都會(huì)創(chuàng)建新的 Heavy 子組件,當(dāng)更新的組件多了,自然就會(huì)造成性能壓力。

而當(dāng)我們使用 v-show 指令,優(yōu)化后的組件模板經(jīng)過(guò)編譯后生成如下渲染函數(shù):

function render() {
  with(this) {
    return _c('div', {
      staticClass: "cell"
    }, [_c('div', {
      directives: [{
        name: "show",
        rawName: "v-show",
        value: (props.value),
        expression: "props.value"
      }],
      staticClass: "on"
    }, [_c('Heavy', {
      attrs: {
        "n": 10000
      }
    })], 1), _c('section', {
      directives: [{
        name: "show",
        rawName: "v-show",
        value: (!props.value),
        expression: "!props.value"
      }],
      staticClass: "off"
    }, [_c('Heavy', {
      attrs: {
        "n": 10000
      }
    })], 1)])
  }
}

當(dāng)條件 props.value 的值變化的時(shí)候,會(huì)觸發(fā)對(duì)應(yīng)的組件更新,對(duì)于 v-show 渲染的節(jié)點(diǎn),由于新舊 vnode 一致,它們只需要一直 patchVnode 即可,那么它又是怎么讓 DOM 節(jié)點(diǎn)顯示和隱藏的呢?

原來(lái)在 patchVnode 過(guò)程中,內(nèi)部會(huì)對(duì)執(zhí)行 v-show 指令對(duì)應(yīng)的鉤子函數(shù) update,然后它會(huì)根據(jù) v-show 指令綁定的值來(lái)設(shè)置它作用的 DOM 元素的 style.display 的值控制顯隱。

因此相比于 v-if 不斷刪除和創(chuàng)建函數(shù)新的 DOM,v-show 僅僅是在更新現(xiàn)有 DOM 的顯隱值,所以 v-show 的開(kāi)銷要比 v-if 小的多,當(dāng)其內(nèi)部 DOM 結(jié)構(gòu)越復(fù)雜,性能的差異就會(huì)越大。

但是 v-show 相比于 v-if 的性能優(yōu)勢(shì)是在組件的更新階段,如果僅僅是在初始化階段,v-if 性能還要高于 v-show,原因是在于它僅僅會(huì)渲染一個(gè)分支,而 v-show 把兩個(gè)分支都渲染了,通過(guò) style.display 來(lái)控制對(duì)應(yīng) DOM 的顯隱。

在使用 v-show 的時(shí)候,所有分支內(nèi)部的組件都會(huì)渲染,對(duì)應(yīng)的生命周期鉤子函數(shù)都會(huì)執(zhí)行,而使用 v-if 的時(shí)候,沒(méi)有命中的分支內(nèi)部的組件是不會(huì)渲染的,對(duì)應(yīng)的生命周期鉤子函數(shù)都不會(huì)執(zhí)行。

因此你要搞清楚它們的原理以及差異,才能在不同的場(chǎng)景使用適合的指令。

KeepAlive

第五個(gè)技巧,使用 KeepAlive 組件緩存 DOM,你可以查看這個(gè)在線示例。

優(yōu)化前的組件代碼如下:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

優(yōu)化后的組件代碼如下:

<template>
  <div id="app">
    <keep-alive>
      <router-view/>
    </keep-alive>
  </div>
</template>

我們點(diǎn)擊按鈕在 Simple page 和 Heavy Page 之間切換,會(huì)渲染不同的視圖,其中 Heavy Page 的渲染非常耗時(shí)。我們開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,然后分別在優(yōu)化前后執(zhí)行如上的操作,會(huì)得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script 的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。

在非優(yōu)化場(chǎng)景下,我們每次點(diǎn)擊按鈕切換路由視圖,都會(huì)重新渲染一次組件,渲染組件就會(huì)經(jīng)過(guò)組件初始化,render、patch 等過(guò)程,如果組件比較復(fù)雜,或者嵌套較深,那么整個(gè)渲染耗時(shí)就會(huì)很長(zhǎng)。

而在使用 KeepAlive 后,被 KeepAlive 包裹的組件在經(jīng)過(guò)第一次渲染后,的 vnode 以及 DOM 都會(huì)被緩存起來(lái),然后再下一次再次渲染該組件的時(shí)候,直接從緩存中拿到對(duì)應(yīng)的 vnode 和 DOM,然后渲染,并不需要再走一次組件初始化,renderpatch 等一系列流程,減少了 script 的執(zhí)行時(shí)間,性能更好。

但是使用 KeepAlive 組件并非沒(méi)有成本,因?yàn)樗鼤?huì)占用更多的內(nèi)存去做緩存,這是一種典型的空間換時(shí)間優(yōu)化思想的應(yīng)用。

Deferred features

第六個(gè)技巧,使用 Deferred 組件延時(shí)分批渲染組件,你可以查看這個(gè)在線示例。

優(yōu)化前的組件代碼如下:

<template>
  <div class="deferred-off">
    <VueIcon icon="fitness_center" class="gigantic"/>

    <h3>I'm an heavy page</h3>

    <Heavy v-for="n in 8" :key="n"/>

    <Heavy class="super-heavy" :n="9999999"/>
  </div>
</template>

優(yōu)化后的組件代碼如下:

<template>
  <div class="deferred-on">
    <VueIcon icon="fitness_center" class="gigantic"/>

    <h3>I'm an heavy page</h3>

    <template v-if="defer(2)">
      <Heavy v-for="n in 8" :key="n"/>
    </template>

    <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/>
  </div>
</template>

<script>
import Defer from '@/mixins/Defer'

export default {
  mixins: [
    Defer(),
  ],
}
</script>

我們點(diǎn)擊按鈕在 Simple page 和 Heavy Page 之間切換,會(huì)渲染不同的視圖,其中 Heavy Page 的渲染非常耗時(shí)。我們開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,然后分別在優(yōu)化前后執(zhí)行如上的操作,會(huì)得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以發(fā)現(xiàn),優(yōu)化前當(dāng)我們從 Simple Page 切到 Heavy Page 的時(shí)候,在一次 Render 接近結(jié)尾的時(shí)候,頁(yè)面渲染的仍然是 Simple Page,會(huì)給人一種頁(yè)面卡頓的感覺(jué)。而優(yōu)化后當(dāng)我們從 Simple Page 切到 Heavy Page 的時(shí)候,在一次 Render 靠前的位置頁(yè)面就已經(jīng)渲染了 Heavy Page 了,并且 Heavy Page 是漸進(jìn)式渲染出來(lái)的。

優(yōu)化前后的差距主要是后者使用了 Defer 這個(gè) mixin,那么它具體是怎么工作的,我們來(lái)一探究竟:

export default function (count = 10) {
  return {
    data () {
      return {
        displayPriority: 0
      }
    },

    mounted () {
      this.runDisplayPriority()
    },

    methods: {
      runDisplayPriority () {
        const step = () => {
          requestAnimationFrame(() => {
            this.displayPriority++
            if (this.displayPriority < count) {
              step()
            }
          })
        }
        step()
      },

      defer (priority) {
        return this.displayPriority >= priority
      }
    }
  }
}

Defer 的主要思想就是把一個(gè)組件的一次渲染拆成多次,它內(nèi)部維護(hù)了 displayPriority 變量,然后在通過(guò) requestAnimationFrame 在每一幀渲染的時(shí)候自增,最多加到 count。然后使用 Defer mixin 的組件內(nèi)部就可以通過(guò) v-if="defer(xxx)" 的方式來(lái)控制在 displayPriority 增加到 xxx 的時(shí)候渲染某些區(qū)塊了。

當(dāng)你有渲染耗時(shí)的組件,使用 Deferred 做漸進(jìn)式渲染是不錯(cuò)的注意,它能避免一次 render 由于 JS 執(zhí)行時(shí)間過(guò)長(zhǎng)導(dǎo)致渲染卡住的現(xiàn)象。

Time slicing

第七個(gè)技巧,使用 Time slicing 時(shí)間片切割技術(shù),你可以查看這個(gè)在線示例。

優(yōu)化前的代碼如下:

fetchItems ({ commit }, { items }) {
  commit('clearItems')
  commit('addItems', items)
}

優(yōu)化后的代碼如下:

fetchItems ({ commit }, { items, splitCount }) {
  commit('clearItems')
  const queue = new JobQueue()
  splitArray(items, splitCount).forEach(
    chunk => queue.addJob(done => {
      // 分時(shí)間片提交數(shù)據(jù)
      requestAnimationFrame(() => {
        commit('addItems', chunk)
        done()
      })
    })
  )
  await queue.start()
}

我們先通過(guò)點(diǎn)擊 Genterate items 按鈕創(chuàng)建 10000 條假數(shù)據(jù),然后分別在開(kāi)啟和關(guān)閉 Time-slicing 的情況下點(diǎn)擊 Commit items 按鈕提交數(shù)據(jù),開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,會(huì)得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以發(fā)現(xiàn),優(yōu)化前總的 script 執(zhí)行時(shí)間要比優(yōu)化后的還要少一些,但是從實(shí)際的觀感上看,優(yōu)化前點(diǎn)擊提交按鈕,頁(yè)面會(huì)卡死 1.2 秒左右,在優(yōu)化后,頁(yè)面不會(huì)完全卡死,但仍然會(huì)有渲染卡頓的感覺(jué)。

那么為什么在優(yōu)化前頁(yè)面會(huì)卡死呢?因?yàn)橐淮涡蕴峤坏臄?shù)據(jù)過(guò)多,內(nèi)部 JS 執(zhí)行時(shí)間過(guò)長(zhǎng),阻塞了 UI 線程,導(dǎo)致頁(yè)面卡死。

優(yōu)化后,頁(yè)面仍有卡頓,是因?yàn)槲覀儾鸱謹(jǐn)?shù)據(jù)的粒度是 1000 條,這種情況下,重新渲染組件仍然有壓力,我們觀察 fps 只有十幾,會(huì)有卡頓感。通常只要讓頁(yè)面的 fps 達(dá)到 60,頁(yè)面就會(huì)非常流暢,如果我們把數(shù)據(jù)拆分粒度變成 100 條,基本上 fps 能達(dá)到 50 以上,雖然頁(yè)面渲染變流暢了,但是完成 10000 條數(shù)據(jù)總的提交時(shí)間還是變長(zhǎng)了。

使用 Time slicing技術(shù)可以避免頁(yè)面卡死,通常我們?cè)谶@種耗時(shí)任務(wù)處理的時(shí)候會(huì)加一個(gè) loading 效果,在這個(gè)示例中,我們可以開(kāi)啟 loading animation,然后提交數(shù)據(jù)。對(duì)比發(fā)現(xiàn),優(yōu)化前由于一次性提交數(shù)據(jù)過(guò)多,JS 一直長(zhǎng)時(shí)間運(yùn)行,阻塞 UI 線程,這個(gè) loading 動(dòng)畫(huà)是不會(huì)展示的,而優(yōu)化后,由于我們拆成多個(gè)時(shí)間片去提交數(shù)據(jù),單次 JS 運(yùn)行時(shí)間變短了,這樣 loading 動(dòng)畫(huà)就有機(jī)會(huì)展示了。

這里要注意的一點(diǎn),雖然我們拆時(shí)間片使用了 requestAnimationFrame API,但是使用 requestAnimationFrame 本身是不能保證滿幀運(yùn)行的,requestAnimationFrame 保證的是在瀏覽器每一次重繪后會(huì)執(zhí)行對(duì)應(yīng)傳入的回調(diào)函數(shù),想要保證滿幀,只能讓 JS 在一個(gè) Tick 內(nèi)的運(yùn)行時(shí)間不超過(guò) 17ms。

Non-reactive data

第八個(gè)技巧,使用 Non-reactive data ,你可以查看這個(gè)在線示例。

優(yōu)化前代碼如下:

const data = items.map(
  item => ({
    id: uid++,
    data: item,
    vote: 0
  })
)

優(yōu)化后代碼如下:

const data = items.map(
  item => optimizeItem(item)
)

function optimizeItem (item) {
  const itemData = {
    id: uid++,
    vote: 0
  }
  Object.defineProperty(itemData, 'data', {
    // Mark as non-reactive
    configurable: false,
    value: item
  })
  return itemData
}

還是前面的示例,我們先通過(guò)點(diǎn)擊 Genterate items 按鈕創(chuàng)建 10000 條假數(shù)據(jù),然后分別在開(kāi)啟和關(guān)閉 Partial reactivity 的情況下點(diǎn)擊 Commit items 按鈕提交數(shù)據(jù),開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,會(huì)得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script 的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。

之所以有這種差異,是因?yàn)閮?nèi)部提交的數(shù)據(jù)的時(shí)候,會(huì)默認(rèn)把新提交的數(shù)據(jù)也定義成響應(yīng)式,如果數(shù)據(jù)的子屬性是對(duì)象形式,還會(huì)遞歸讓子屬性也變成響應(yīng)式,因此當(dāng)提交數(shù)據(jù)很多的時(shí)候,這個(gè)過(guò)程就變成了一個(gè)耗時(shí)過(guò)程。

而優(yōu)化后我們把新提交的數(shù)據(jù)中的對(duì)象屬性 data 手動(dòng)變成了 configurablefalse,這樣內(nèi)部在 walk 時(shí)通過(guò) Object.keys(obj) 獲取對(duì)象屬性數(shù)組會(huì)忽略 data,也就不會(huì)為 data 這個(gè)屬性 defineReactive,由于 data 指向的是一個(gè)對(duì)象,這樣也就會(huì)減少遞歸響應(yīng)式的邏輯,相當(dāng)于減少了這部分的性能損耗。數(shù)據(jù)量越大,這種優(yōu)化的效果就會(huì)更明顯。

其實(shí)類似這種優(yōu)化的方式還有很多,比如我們?cè)诮M件中定義的一些數(shù)據(jù),也不一定都要在 data 中定義。有些數(shù)據(jù)我們并不是用在模板中,也不需要監(jiān)聽(tīng)它的變化,只是想在組件的上下文中共享這個(gè)數(shù)據(jù),這個(gè)時(shí)候我們可以僅僅把這個(gè)數(shù)據(jù)掛載到組件實(shí)例 this 上,例如:

export default {
  created() {
    this.scroll = null
  },
  mounted() {
    this.scroll = new BScroll(this.$el)
  }
}

這樣我們就可以在組件上下文中共享 scroll 對(duì)象了,即使它不是一個(gè)響應(yīng)式對(duì)象。

Virtual scrolling

第九個(gè)技巧,使用 Virtual scrolling ,你可以查看這個(gè)在線示例。

優(yōu)化前組件的代碼如下:

<div class="items no-v">
  <FetchItemViewFunctional
    v-for="item of items"
    :key="item.id"
    :item="item"
    @vote="voteItem(item)"
  />
</div>

優(yōu)化后代碼如下:

<recycle-scroller
  class="items"
  :items="items"
  :item-size="24"
>
  <template v-slot="{ item }">
    <FetchItemView
      :item="item"
      @vote="voteItem(item)"
    />
  </template>
</recycle-scroller>

還是前面的示例,我們需要開(kāi)啟 View list,然后點(diǎn)擊 Genterate items 按鈕創(chuàng)建 10000 條假數(shù)據(jù)(注意,線上示例最多只能創(chuàng)建 1000 條數(shù)據(jù),實(shí)際上 1000 條數(shù)據(jù)并不能很好地體現(xiàn)優(yōu)化的效果,所以我修改了源碼的限制,本地運(yùn)行,創(chuàng)建了 10000 條數(shù)據(jù)),然后分別在 UnoptimizedRecycleScroller 的情況下點(diǎn)擊 Commit items 按鈕提交數(shù)據(jù),滾動(dòng)頁(yè)面,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,會(huì)得到如下結(jié)果。

優(yōu)化前:

Vue.js實(shí)用的性能優(yōu)化技巧分享

優(yōu)化后:

Vue.js實(shí)用的性能優(yōu)化技巧分享

對(duì)比這兩張圖我們發(fā)現(xiàn),在非優(yōu)化的情況下,10000 條數(shù)據(jù)在滾動(dòng)情況下 fps 只有個(gè)位數(shù),在非滾動(dòng)情況下也就十幾,原因是非優(yōu)化場(chǎng)景下渲染的 DOM 太多,渲染本身的壓力很大。優(yōu)化后,即使 10000 條數(shù)據(jù),在滾動(dòng)情況下的 fps 也能有 30 多,在非滾動(dòng)情況下可以達(dá)到 60 滿幀。

之所以有這個(gè)差異,是因?yàn)樘摂M滾動(dòng)的實(shí)現(xiàn)方式:是只渲染視口內(nèi)的 DOM。這樣總共渲染的 DOM 數(shù)量就很少了,自然性能就會(huì)好很多。

虛擬滾動(dòng)組件也是 Guillaume Chau 寫的,感興趣的同學(xué)可以去研究它的源碼實(shí)現(xiàn)。它的基本原理就是監(jiān)聽(tīng)滾動(dòng)事件,動(dòng)態(tài)更新需要顯示的 DOM 元素,計(jì)算出它們?cè)谝晥D中的位移。

虛擬滾動(dòng)組件也并非沒(méi)有成本,因?yàn)樗枰跐L動(dòng)的過(guò)程中實(shí)時(shí)去計(jì)算,所以會(huì)有一定的 script 執(zhí)行的成本。因此如果列表的數(shù)據(jù)量不是很大的情況,我們使用普通的滾動(dòng)就足夠了。

到此,相信大家對(duì)“Vue.js實(shí)用的性能優(yōu)化技巧分享”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI