Vue.js性能優(yōu)化N個技巧(值得收藏)
這篇文章主要參考了 Vue.js 核心成員Guillaume Chau 在 19 年美國的 Vue conf 分享的主題:9 Performance secrets revealed,分享中提到了九個 Vue.js 性能優(yōu)化的技巧。
我看完他的分享 PPT后,也閱讀了相關(guān)的項目源碼,在深入了解它的優(yōu)化原理后,把其中一些優(yōu)化技巧也應用到了我平時的工作中,取得了相當不錯的效果。
這個分享可謂是非常實用了,但是知道和關(guān)注的人似乎并不多,到目前為止,該項目也只有可憐的幾百個 star。雖然距大佬的分享已經(jīng)有兩年時間,但是其中的優(yōu)化技巧并沒有過時,為了讓更多的人了解并學習到其中的實用技巧,我決定對他的分享做二次加工,詳細闡述其中的優(yōu)化原理,并做一定程度的擴展和延伸。
本文主要還是針對 Vue.js 2.x 版本,畢竟接下來一段時間,Vue.js 2.x 還是我們工作中的主流版本。
我建議你在學習這篇文章的時候可以拉取項目的源碼,并且本地運行,查看優(yōu)化前后的效果差異。
Functional components
第一個技巧,函數(shù)式組件,你可以查看這個在線示例
優(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>
然后我們在父組件各渲染優(yōu)化前后的組件 800 個,并在每一幀內(nèi)部通過修改數(shù)據(jù)來觸發(fā)組件的更新,開啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化前執(zhí)行 script
的時間要多于優(yōu)化后的,而我們知道 JS 引擎是單線程的運行機制,JS 線程會阻塞 UI 線程,所以當腳本執(zhí)行時間過長,就會阻塞渲染,導致頁面卡頓。而優(yōu)化后的 script
執(zhí)行時間短,所以它的性能更好。
那么,為什么用函數(shù)式組件 JS 的執(zhí)行時間就變短了呢?這要從函數(shù)式組件的實現(xiàn)原理說起了,你可以把它理解成一個函數(shù),它可以根據(jù)你傳遞的上下文數(shù)據(jù)渲染生成一片 DOM。
函數(shù)式組件和普通的對象類型的組件不同,它不會被看作成一個真正的組件,我們知道在 patch
過程中,如果遇到一個節(jié)點是組件 vnode
,會遞歸執(zhí)行子組件的初始化過程;而函數(shù)式組件的 render
生成的是普通的 vnode
,不會有遞歸子組件的過程,因此渲染開銷會低很多。
因此,函數(shù)式組件也不會有狀態(tài),不會有響應式數(shù)據(jù),生命周期鉤子函數(shù)這些東西。你可以把它當成把普通組件模板中的一部分 DOM 剝離出來,通過函數(shù)的方式渲染出來,是一種在 DOM 層面的復用。
Child component splitting
第二個技巧,子組件拆分,你可以查看這個在線示例。
優(yōu)化前的組件代碼如下:
<template> <div :style="{ opacity: number / 300 }"> <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 :style="{ opacity: number / 300 }"> <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>
然后我們在父組件各渲染優(yōu)化前后的組件 300 個,并在每一幀內(nèi)部通過修改數(shù)據(jù)來觸發(fā)組件的更新,開啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時間要明顯少于優(yōu)化前的,因此性能體驗更好。
那么為什么會有差異呢,我們來看優(yōu)化前的組件,示例通過一個 heavy
函數(shù)模擬了一個耗時的任務,且這個函數(shù)在每次渲染的時候都會執(zhí)行一次,所以每次組件的渲染都會消耗較長的時間執(zhí)行 JavaScript。
而優(yōu)化后的方式是把這個耗時任務 heavy
函數(shù)的執(zhí)行邏輯用子組件 ChildComp
封裝了,由于 Vue 的更新是組件粒度的,雖然每一幀都通過數(shù)據(jù)修改導致了父組件的重新渲染,但是 ChildComp
卻不會重新渲染,因為它的內(nèi)部也沒有任何響應式數(shù)據(jù)的變化。所以優(yōu)化后的組件不會在每次渲染都執(zhí)行耗時任務,自然執(zhí)行的 JavaScript 時間就變少了。
不過針對這個優(yōu)化的方式我提出了一些不同的看法,詳情可以點開這個 issue,我認為這個場景下的優(yōu)化用計算屬性要比子組件拆分要好。得益于計算屬性自身緩存特性,耗時的邏輯也只會在第一次渲染的時候執(zhí)行,而且使用計算屬性也沒有額外渲染子組件的開銷。
在實際工作中,使用計算屬性是優(yōu)化性能的場景會有很多,畢竟它也體現(xiàn)了一種空間換時間的優(yōu)化思想。
Local variables
第三個技巧,局部變量,你可以查看這個在線示例。
優(yōu)化前的組件代碼如下:
<template> <div :style="{ opacity: start / 300 }">{{ 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 :style="{ opacity: start / 300 }">{{ 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>
然后我們在父組件各渲染優(yōu)化前后的組件 300 個,并在每一幀內(nèi)部通過修改數(shù)據(jù)來觸發(fā)組件的更新,開啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時間要明顯少于優(yōu)化前的,因此性能體驗更好。
這里主要是優(yōu)化前后組件的計算屬性 result
的實現(xiàn)差異,優(yōu)化前的組件多次在計算過程中訪問 this.base
,而優(yōu)化后的組件會在計算前先用局部變量 base
緩存 this.base
,后面則直接訪問 base
變量。
那么為啥這個差異會造成性能上的差異呢,原因是你每次訪問 this.base
的時候,由于 this.base
是一個響應式對象,所以會觸發(fā)它的 getter
,進而會執(zhí)行依賴收集相關(guān)邏輯代碼。類似的邏輯執(zhí)行多了,像示例這樣,幾百次循環(huán)更新幾百個組件,每個組件觸發(fā) computed
重新計算,然后又多次執(zhí)行依賴收集相關(guān)邏輯,性能自然就下降了。
從需求上來說,this.base
執(zhí)行一次依賴收集就夠了,因此我們只需要把它的 getter
求值結(jié)果返回給局部變量 base
,后續(xù)再次訪問 base
的時候就不會觸發(fā) getter
,也不會走依賴收集的邏輯了,性能自然就得到了提升。
這是一個非常實用的性能優(yōu)化技巧。因為很多人在開發(fā) Vue.js 項目的時候,每當取變量的時候就習慣性直接寫 this.xxx
了,因為大部分人并不會注意到訪問 this.xxx
背后做的事情。在訪問次數(shù)不多的時候,性能問題并沒有凸顯,但是一旦訪問次數(shù)變多,比如在一個大循環(huán)中多次訪問,類似示例這種場景,就會產(chǎn)生性能問題了。
我之前給 ZoomUI 的 Table 組件做性能優(yōu)化的時候,在 render table body
的時候就使用了局部變量的優(yōu)化技巧,并寫了 benchmark 做性能對比:渲染 1000 * 10 的表格,ZoomUI Table 的更新數(shù)據(jù)重新渲染的性能要比 ElementUI 的 Table 性能提升了近一倍。
Reuse DOM with v-show
第四個技巧,使用 v-show
復用 DOM,你可以查看這個在線示例。
優(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>
然后我們在父組件各渲染優(yōu)化前后的組件 200 個,并在每一幀內(nèi)部通過修改數(shù)據(jù)來觸發(fā)組件的更新,開啟 Chrome 的 Performance 面板記錄它們的性能,得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時間要明顯少于優(yōu)化前的,因此性能體驗更好。
優(yōu)化前后的主要區(qū)別是用 v-show
指令替代了 v-if
指令來替代組件的顯隱,雖然從表現(xiàn)上看,v-show
和 v-if
類似,都是控制組件的顯隱,但內(nèi)部實現(xiàn)差距還是很大的。
v-if
指令在編譯階段就會編譯成一個三元運算符,條件渲染,比如優(yōu)化前的組件模板經(jīng)過編譯后生成如下渲染函數(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)]) } }
當條件 props.value
的值變化的時候,會觸發(fā)對應的組件更新,對于 v-if
渲染的節(jié)點,由于新舊節(jié)點 vnode
不一致,在核心 diff 算法比對過程中,會移除舊的 vnode
節(jié)點,創(chuàng)建新的 vnode
節(jié)點,那么就會創(chuàng)建新的 Heavy
組件,又會經(jīng)歷 Heavy
組件自身初始化、渲染 vnode
、patch
等過程。
因此使用 v-if
每次更新組件都會創(chuàng)建新的 Heavy
子組件,當更新的組件多了,自然就會造成性能壓力。
而當我們使用 v-show
指令,優(yōu)化后的組件模板經(jīng)過編譯后生成如下渲染函數(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)]) } }
當條件 props.value
的值變化的時候,會觸發(fā)對應的組件更新,對于 v-show
渲染的節(jié)點,由于新舊 vnode
一致,它們只需要一直 patchVnode
即可,那么它又是怎么讓 DOM 節(jié)點顯示和隱藏的呢?
原來在 patchVnode
過程中,內(nèi)部會對執(zhí)行 v-show
指令對應的鉤子函數(shù) update
,然后它會根據(jù) v-show
指令綁定的值來設置它作用的 DOM 元素的 style.display
的值控制顯隱。
因此相比于 v-if
不斷刪除和創(chuàng)建函數(shù)新的 DOM,v-show
僅僅是在更新現(xiàn)有 DOM 的顯隱值,所以 v-show
的開銷要比 v-if
小的多,當其內(nèi)部 DOM 結(jié)構(gòu)越復雜,性能的差異就會越大。
但是 v-show
相比于 v-if
的性能優(yōu)勢是在組件的更新階段,如果僅僅是在初始化階段,v-if
性能還要高于 v-show
,原因是在于它僅僅會渲染一個分支,而 v-show
把兩個分支都渲染了,通過 style.display
來控制對應 DOM 的顯隱。
在使用 v-show
的時候,所有分支內(nèi)部的組件都會渲染,對應的生命周期鉤子函數(shù)都會執(zhí)行,而使用 v-if
的時候,沒有命中的分支內(nèi)部的組件是不會渲染的,對應的生命周期鉤子函數(shù)都不會執(zhí)行。
因此你要搞清楚它們的原理以及差異,才能在不同的場景使用適合的指令。
KeepAlive
第五個技巧,使用 KeepAlive
組件緩存 DOM,你可以查看這個在線示例。
優(yōu)化前的組件代碼如下:
<template> <div id="app"> <router-view/> </div> </template>
優(yōu)化后的組件代碼如下:
<template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template>
我們點擊按鈕在 Simple page 和 Heavy Page 之間切換,會渲染不同的視圖,其中 Heavy Page 的渲染非常耗時。我們開啟 Chrome 的 Performance 面板記錄它們的性能,然后分別在優(yōu)化前后執(zhí)行如上的操作,會得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時間要明顯少于優(yōu)化前的,因此性能體驗更好。
在非優(yōu)化場景下,我們每次點擊按鈕切換路由視圖,都會重新渲染一次組件,渲染組件就會經(jīng)過組件初始化,render
、patch
等過程,如果組件比較復雜,或者嵌套較深,那么整個渲染耗時就會很長。
而在使用 KeepAlive
后,被 KeepAlive
包裹的組件在經(jīng)過第一次渲染后,的 vnode
以及 DOM 都會被緩存起來,然后再下一次再次渲染該組件的時候,直接從緩存中拿到對應的 vnode
和 DOM,然后渲染,并不需要再走一次組件初始化,render
和 patch
等一系列流程,減少了 script
的執(zhí)行時間,性能更好。
但是使用 KeepAlive
組件并非沒有成本,因為它會占用更多的內(nèi)存去做緩存,這是一種典型的空間換時間優(yōu)化思想的應用。
Deferred features
第六個技巧,使用 Deferred
組件延時分批渲染組件,你可以查看這個在線示例。
優(yōu)化前的組件代碼如下:
<template> <div class="deferred-off"> <VueIcon icon="fitness_center" class="gigantic"/> <h2>I'm an heavy page</h2> <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"/> <h2>I'm an heavy page</h2> <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>
我們點擊按鈕在 Simple page 和 Heavy Page 之間切換,會渲染不同的視圖,其中 Heavy Page 的渲染非常耗時。我們開啟 Chrome 的 Performance 面板記錄它們的性能,然后分別在優(yōu)化前后執(zhí)行如上的操作,會得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以發(fā)現(xiàn),優(yōu)化前當我們從 Simple Page 切到 Heavy Page 的時候,在一次 Render 接近結(jié)尾的時候,頁面渲染的仍然是 Simple Page,會給人一種頁面卡頓的感覺。而優(yōu)化后當我們從 Simple Page 切到 Heavy Page 的時候,在一次 Render 靠前的位置頁面就已經(jīng)渲染了 Heavy Page 了,并且 Heavy Page 是漸進式渲染出來的。
優(yōu)化前后的差距主要是后者使用了 Defer
這個 mixin
,那么它具體是怎么工作的,我們來一探究竟:
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
的主要思想就是把一個組件的一次渲染拆成多次,它內(nèi)部維護了 displayPriority
變量,然后在通過 requestAnimationFrame
在每一幀渲染的時候自增,最多加到 count
。然后使用 Defer mixin
的組件內(nèi)部就可以通過 v-if="defer(xxx)"
的方式來控制在 displayPriority
增加到 xxx
的時候渲染某些區(qū)塊了。
當你有渲染耗時的組件,使用 Deferred
做漸進式渲染是不錯的注意,它能避免一次 render
由于 JS 執(zhí)行時間過長導致渲染卡住的現(xiàn)象。
Time slicing
第七個技巧,使用 Time slicing
時間片切割技術(shù),你可以查看這個在線示例。
優(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ù)據(jù) requestAnimationFrame(() => { commit('addItems', chunk) done() }) }) ) await queue.start() }
我們先通過點擊 Genterate items
按鈕創(chuàng)建 10000 條假數(shù)據(jù),然后分別在開啟和關(guān)閉 Time-slicing
的情況下點擊 Commit items
按鈕提交數(shù)據(jù),開啟 Chrome 的 Performance 面板記錄它們的性能,會得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以發(fā)現(xiàn),優(yōu)化前總的 script
執(zhí)行時間要比優(yōu)化后的還要少一些,但是從實際的觀感上看,優(yōu)化前點擊提交按鈕,頁面會卡死 1.2 秒左右,在優(yōu)化后,頁面不會完全卡死,但仍然會有渲染卡頓的感覺。
那么為什么在優(yōu)化前頁面會卡死呢?因為一次性提交的數(shù)據(jù)過多,內(nèi)部 JS 執(zhí)行時間過長,阻塞了 UI 線程,導致頁面卡死。
優(yōu)化后,頁面仍有卡頓,是因為我們拆分數(shù)據(jù)的粒度是 1000 條,這種情況下,重新渲染組件仍然有壓力,我們觀察 fps 只有十幾,會有卡頓感。通常只要讓頁面的 fps 達到 60,頁面就會非常流暢,如果我們把數(shù)據(jù)拆分粒度變成 100 條,基本上 fps 能達到 50 以上,雖然頁面渲染變流暢了,但是完成 10000 條數(shù)據(jù)總的提交時間還是變長了。
使用 Time slicing
技術(shù)可以避免頁面卡死,通常我們在這種耗時任務處理的時候會加一個 loading
效果,在這個示例中,我們可以開啟 loading animation
,然后提交數(shù)據(jù)。對比發(fā)現(xiàn),優(yōu)化前由于一次性提交數(shù)據(jù)過多,JS 一直長時間運行,阻塞 UI 線程,這個 loading
動畫是不會展示的,而優(yōu)化后,由于我們拆成多個時間片去提交數(shù)據(jù),單次 JS 運行時間變短了,這樣 loading
動畫就有機會展示了。
這里要注意的一點,雖然我們拆時間片使用了
requestAnimationFrame
API,但是使用requestAnimationFrame
本身是不能保證滿幀運行的,requestAnimationFrame
保證的是在瀏覽器每一次重繪后會執(zhí)行對應傳入的回調(diào)函數(shù),想要保證滿幀,只能讓 JS 在一個 Tick 內(nèi)的運行時間不超過 17ms。
Non-reactive data
第八個技巧,使用 Non-reactive data
,你可以查看這個在線示例。
優(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 }
還是前面的示例,我們先通過點擊 Genterate items
按鈕創(chuàng)建 10000 條假數(shù)據(jù),然后分別在開啟和關(guān)閉 Partial reactivity
的情況下點擊 Commit items
按鈕提交數(shù)據(jù),開啟 Chrome 的 Performance 面板記錄它們的性能,會得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們可以看到優(yōu)化后執(zhí)行 script
的時間要明顯少于優(yōu)化前的,因此性能體驗更好。
之所以有這種差異,是因為內(nèi)部提交的數(shù)據(jù)的時候,會默認把新提交的數(shù)據(jù)也定義成響應式,如果數(shù)據(jù)的子屬性是對象形式,還會遞歸讓子屬性也變成響應式,因此當提交數(shù)據(jù)很多的時候,這個過程就變成了一個耗時過程。
而優(yōu)化后我們把新提交的數(shù)據(jù)中的對象屬性 data
手動變成了 configurable
為 false
,這樣內(nèi)部在 walk
時通過 Object.keys(obj)
獲取對象屬性數(shù)組會忽略 data
,也就不會為 data
這個屬性 defineReactive
,由于 data
指向的是一個對象,這樣也就會減少遞歸響應式的邏輯,相當于減少了這部分的性能損耗。數(shù)據(jù)量越大,這種優(yōu)化的效果就會更明顯。
其實類似這種優(yōu)化的方式還有很多,比如我們在組件中定義的一些數(shù)據(jù),也不一定都要在 data
中定義。有些數(shù)據(jù)我們并不是用在模板中,也不需要監(jiān)聽它的變化,只是想在組件的上下文中共享這個數(shù)據(jù),這個時候我們可以僅僅把這個數(shù)據(jù)掛載到組件實例 this
上,例如:
export default { created() { this.scroll = null }, mounted() { this.scroll = new BScroll(this.$el) } }
這樣我們就可以在組件上下文中共享 scroll
對象了,即使它不是一個響應式對象。
Virtual scrolling
第九個技巧,使用 Virtual scrolling
,你可以查看這個在線示例。
優(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>
還是前面的示例,我們需要開啟 View list
,然后點擊 Genterate items
按鈕創(chuàng)建 10000 條假數(shù)據(jù)(注意,線上示例最多只能創(chuàng)建 1000 條數(shù)據(jù),實際上 1000 條數(shù)據(jù)并不能很好地體現(xiàn)優(yōu)化的效果,所以我修改了源碼的限制,本地運行,創(chuàng)建了 10000 條數(shù)據(jù)),然后分別在 Unoptimized
和 RecycleScroller
的情況下點擊 Commit items
按鈕提交數(shù)據(jù),滾動頁面,開啟 Chrome 的 Performance 面板記錄它們的性能,會得到如下結(jié)果。
優(yōu)化前:
優(yōu)化后:
對比這兩張圖我們發(fā)現(xiàn),在非優(yōu)化的情況下,10000 條數(shù)據(jù)在滾動情況下 fps 只有個位數(shù),在非滾動情況下也就十幾,原因是非優(yōu)化場景下渲染的 DOM 太多,渲染本身的壓力很大。優(yōu)化后,即使 10000 條數(shù)據(jù),在滾動情況下的 fps 也能有 30 多,在非滾動情況下可以達到 60 滿幀。
之所以有這個差異,是因為虛擬滾動的實現(xiàn)方式:是只渲染視口內(nèi)的 DOM。這樣總共渲染的 DOM 數(shù)量就很少了,自然性能就會好很多。
虛擬滾動組件也是 Guillaume Chau 寫的,感興趣的同學可以去研究它的源碼實現(xiàn)。它的基本原理就是監(jiān)聽滾動事件,動態(tài)更新需要顯示的 DOM 元素,計算出它們在視圖中的位移。
虛擬滾動組件也并非沒有成本,因為它需要在滾動的過程中實時去計算,所以會有一定的 script
執(zhí)行的成本。因此如果列表的數(shù)據(jù)量不是很大的情況,我們使用普通的滾動就足夠了。
總結(jié)
通過這篇文章,我希望你能了解到 Vue.js 的九種性能優(yōu)化技巧,并能運用到實際的開發(fā)項目中。除了上述技巧之外,還有懶加載圖片、懶加載組件、異步組件等等常用的性能優(yōu)化手段。
在做性能優(yōu)化前,我們需要分析性能的瓶頸在哪,才能因地制宜。另外,性能優(yōu)化都需要數(shù)據(jù)支撐的,你在做任何性能優(yōu)化前,需要先采集優(yōu)化前的數(shù)據(jù),這樣優(yōu)化后才能夠通過數(shù)據(jù)對比看到優(yōu)化的效果。
希望你在日后的開發(fā)過程中,不再只滿足于實現(xiàn)需求,寫每一行代碼的時候,都能思考它可能產(chǎn)生的性能方面的影響。
參考資料
[1] vue-9-perf-secrets slidse:https://slides.com/akryum/vueconfus-2019
[2] vue-9-perf-secrets 分享演講視頻:https://www.vuemastery.com/conferences/vueconf-us-2019/9-performance-secrets-revealed/
[3] vue-9-perf-secrets 項目源碼:https://github.com/Akryum/vue-9-perf-secrets
[4] vue-9-perf-secrets 在線演示地址:https://vue-9-perf-secrets.netlify.app/
[5] vue-9-perf-secrets 討論 issue:https://github.com/Akryum/vue-9-perf-secrets/issues/1
[6] vue-virtual-scroller 項目源碼:https://github.com/Akryum/vue-virtual-scroller
到此這篇關(guān)于Vue.js九個性能優(yōu)化技巧(值得收藏)的文章就介紹到這了,更多相關(guān)Vue.js性能優(yōu)化技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3后臺管理系統(tǒng)之創(chuàng)建和配置項目
后臺管理系統(tǒng)是我們?nèi)粘i_發(fā)學習經(jīng)常遇到的一個項目,下面這篇文章主要給大家介紹了關(guān)于Vue3后臺管理系統(tǒng)之創(chuàng)建和配置項目的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-09-09Vue項目頁面跳轉(zhuǎn)時瀏覽器窗口上方顯示進度條功能
這篇文章主要介紹了Vue項目頁面跳轉(zhuǎn)時瀏覽器窗口上方顯示進度條功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03vuex 中輔助函數(shù)mapGetters的基本用法詳解
mapGetters輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計算屬性,在組件或界面中不使用mapGetter調(diào)用映射vuex中的getter,在組件或界面中使用mapGetter調(diào)用映射vuex中的getter,具體內(nèi)容跟隨小編一起通過本文學習吧2021-07-07Vue.js 中取得后臺原生HTML字符串 原樣顯示問題的解決方法
這篇文章主要介紹了VUE.js 中取得后臺原生HTML字符串 原樣顯示問題 ,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06vue-devtools?開發(fā)工具插件之支持vue3?chrome?瀏覽器插件
這篇文章主要介紹了vue-devtools?開發(fā)工具插件之支持vue3?chrome?瀏覽器插件,用這個版本主要是為了支持vue3?推薦直接下載,文中給大家提供了下載地址,感興趣的朋友跟隨小編一起看看吧2022-01-01el-form表單el-form-item label不換行問題及解決
這篇文章主要介紹了el-form表單el-form-item label不換行問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10