您好,登錄后才能下訂單哦!
本篇內(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
第一個(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)化前:
優(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ù)用。
第二個(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)化前:
優(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)化思想。
第三個(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)化前:
優(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 性能提升了近一倍。
第四個(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)化前:
優(yōu)化后:
對(duì)比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時(shí)間要明顯少于優(yōu)化前的,因此性能體驗(yàn)更好。
優(yōu)化前后的主要區(qū)別是用 v-show
指令替代了 v-if
指令來(lái)替代組件的顯隱,雖然從表現(xiàn)上看,v-show
和 v-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
組件自身初始化、渲染 vnode
、patch
等過(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)景使用適合的指令。
第五個(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)化前:
優(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,然后渲染,并不需要再走一次組件初始化,render
和 patch
等一系列流程,減少了 script
的執(zhí)行時(shí)間,性能更好。
但是使用 KeepAlive
組件并非沒(méi)有成本,因?yàn)樗鼤?huì)占用更多的內(nèi)存去做緩存,這是一種典型的空間換時(shí)間優(yōu)化思想的應(yīng)用。
第六個(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)化前:
優(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)象。
第七個(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)化前:
優(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。
第八個(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)化前:
優(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)變成了 configurable
為 false
,這樣內(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ì)象。
第九個(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ù)),然后分別在 Unoptimized
和 RecycleScroller
的情況下點(diǎn)擊 Commit items
按鈕提交數(shù)據(jù),滾動(dòng)頁(yè)面,開(kāi)啟 Chrome 的 Performance 面板記錄它們的性能,會(huì)得到如下結(jié)果。
優(yōu)化前:
優(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í)!
免責(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)容。