隨着互聯網的發展,愈來愈多的公司都在使用Vue
可是隨着項目的愈來愈大,不免的會帶來一系列的性能問題,筆者也爲了這些問題而感到頭疼,也一樣的針對Vue
的性能優化進行學習,已便在項目之出把性能問題規避掉,避免沒有必要的返工。javascript
爲了方便之後可以快速的找到相關學習內容,在這裏作一下記錄,方便之後查看,同時也想把這些內容總結一下但願可以幫助更多的小夥伴一塊兒學習,一塊兒成長。Fighting~html
這個時候可能會有不少小夥伴說,如今Vue3.0
都快發佈了爲何還要優化2.0
的項目?由於公司80%的項目全是Vue2.0
的項目,遷移的話成本過高,因此只能進行性能的優化調整。廢話就很少贅述了,直接開始吧。前端
活用異步組件
Vue-cli
打包的時候會把全部依賴的文件打包成一個很大的一個js
文件中,當用戶瀏覽網頁的時候須要把整個js
文件拉取過來,這樣會致使頁面在初始化的時候,頁面會出現長時間的白屏狀況,這個問題確實是蠻棘手的。vue
設想一下若是在頁面中有不少的功能點,每一個功能點又對應着不一樣的功能彈窗或者表單,首先第一點頁面中的功能不少,咱們不知道用戶想要使用哪一個功能,須要彈出哪一個彈窗或表單,若是異步組件的狀況,Vue-cli
在打包的時候會把異步組件單獨打包成一個文件,當用戶使用的纔會去加載這個js
文件內容,這樣不管是首屏的渲染起到了必定的優化的做用。java
看下官網對於異步組件的說明:webpack
在大型應用中,咱們可能須要將應用分割成小一些的代碼塊,而且只在須要的時候才從服務器加載一個模塊。爲了簡化,Vue
容許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue
只有在這個組件須要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供將來重渲染。web
// 代碼截取自Vue官網 Vue.component('async-webpack-example', () => import('./my-async-component') )
Vue
官方爲了解決組件加載時的等待過長,提供了異步組件加載Loading
的異步組件:算法
// 代碼截取自Vue官網 const AsyncComponent = () => ({ // 須要加載的組件 (應該是一個 `Promise` 對象) component: import('./MyComponent.vue'), // 異步組件加載時使用的組件 loading: LoadingComponent, // 加載失敗時使用的組件 error: ErrorComponent, // 展現加載時組件的延時時間。默認值是 200 (毫秒) delay: 200, // 若是提供了超時時間且組件加載也超時了, // 則使用加載失敗時使用的組件。默認值是:`Infinity` timeout: 3000 })
使用異步組件須要注意如下幾點:後端
setTimeOut
也不是何嘗不可的。v-if
,否則會報錯,說test
組件未註冊。v-if
是惰性的,只有當第一次值爲true
時纔會開始初始化。初始化減小DOM的渲染
仍然時上面所說的狀況,頁面中功能點不少,可是又不少彈窗什麼的,其實對於這些彈窗最開始的時候考慮全部的彈窗只使用一個彈窗,爲了節約頁面初始化的渲染,可是在實際開發過程當中,雖然解決了一部分問題,彷彿在開發過程當中並非那麼樂觀,在彈窗內部出現了大量的v-if
和v-show
對於維護來講太難了。數組
也有想過使用<component/>
組件,可是一個<component/>
所承受的壓力可想而知不是一點半點的。可是這個問題任然是存在的須要獲得解決。沒有辦法的狀況下,最後使用了兩個flag
去控制彈窗的顯示與隱藏。
<template> <div> <el-dialog title="提示" v-if="isRenderDialog" :visible.sync="isShowDialog"></el-dialog> <el-button @click="onShowDialog">Render Dialog</el-button> </div> </template> <script> export default { data:() => ({ isRenderDialog:false, isShowDialog:false }), methods: { onShowDialog(){ !this.isRenderDialog && (this.isRenderDialog = true); this.$nextTick(() => { this.isShowDialog = true; }) } } } </script>
上述代碼中使用兩個flag
值控制Dialog
一個是控制Dialog
的渲染,一個控制Dialog
的顯示,當用戶首次進入頁面的時候則dialog
元素不會被渲染,當用戶點擊按鈕,對應的Dialog
纔會被渲染出來,當Dialog
的DOM
渲染完成使用在使用顯示Dialog
。
注:在$nextTick
中顯示dialog
是爲了保證dialog
的動畫效果,若是不使用$nextTick
則dialog
就會很生硬的出現。
組件內部請求數據
你們在作業務的時候,可能會有這種狀況,當點擊按鈕以後,須要獲取到該條數據的詳情渲染到彈窗或者側滑中,這種狀況必定不在少數啦。筆者在開始作這個的時候就是,在點擊的時候直接去獲取點擊的元素的詳情數據,當數據返回以後把數據放到data
中緩存,以後再傳到組件中。
這樣作不是不可行的,也是能夠的,這樣就會面臨一個問題,第一點就是當彈窗中的渲染的元素過多的狀況下,側滑或者彈窗的動畫效果會很卡,有的時候甚至是不動,瞬間就消失了。
最後通過反覆的實驗,把數據放到彈窗內部組件中去請求,保證彈窗或者側滑出現的時候內置元素較少,當數據沒有請求回來以前須要把彈框組件內的全部元素隱藏,使用loading
代替,當彈窗或者側滑關閉的使用須要把顯示的組件銷燬掉,保證裏面的數據所佔用的內存被釋放,這樣對於總體優化仍是有一些幫助的。
tamplate少計算
因爲業務狀況的複雜程度,不免會某一個地方添加各類條件的渲染,例如:v-if="isHide && selectList.length && (isA || isB)"
,這裏也只是舉一個簡單的栗子可能在實際的開發過程當中的狀況遠比這個要複雜的多,這種表達式看上去雖說是能夠維護的,可是久而久之下去就會暴露問題,這樣作是很不利於維護的。
對於這種狀況能夠適當的使用methods
或computed
封裝成方法,其實這樣作的好處是方柏霓咱們判斷相同的表達式,若是其餘的元素也有相似的需求能夠直接使用這個方法。
v-for && v-bind:key
在使用v-for
循環過程當中,使用:key="item.id"
這樣的代碼對於代碼的性能是很不友好的,由於當data
數據更新的時候,新的狀態值會和舊的狀態值作對比,Vue
在多diff
算法的時候可以更快的定位到虛擬DOM
的元素上。
其實說到這裏就須要說明如下key
在vue
中到底起到一個什麼樣的做用,key
屬性實際上是vue
的一個優化,上文也說了就是爲了更精準高效的定位到虛擬DOM
,至關於使用key
給數組中某個預算綁定到了一塊兒,若是那個key
對應的數據發生了變化,直接更新對應的DOM
就能夠了。
對於簡短的for
來講能夠直接使用index
做爲key
可是,若是大型列表的話最好仍是不要使用index
做爲key
了。舉個栗子,例如數組刪除了一個元素,那麼這個元素後方元素的下標全都前移了一位,以前key
對應的數據和dom
就會亂了,除非從新匹配key
,那就容易產生錯誤。若是從新匹配key
,等於所有從新渲染一遍,違背了使用key
來優化更新dom
的初衷。可是若是對於Vue
玩的很透的同窗來講能夠能夠忽略這一條。
Object.freeze
若是對Vue
有必定了解的小夥伴都知道Vue
是經過Object.defineProperty
對數據進行挾持,來最終實現視圖響應數據的變化,可是在實際的開發過程當中,頁面中有一部分可能不須要進行雙向綁定,只是作單純的渲染,數據一旦綁定以後不須要再作出任何改變的時候可使用Object.freeze
對數據作解綁。
先介紹如下Object.freeze
內置函數,用於對接對象,凍結後的對象不會在被修改,不能對這個對象進行添加新屬性, 不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性,可配置性,可寫性.此外凍結一個對象後該對象的原型也不能進行修改。
當數據量大的時候,這可以很明顯的減小組件初始化的時間,這裏有一個須要注意的點就是一旦被凍結的對象就不再能被修改了。可是這裏有一個問題須要注意的是,嗒嗒嗒,敲黑板!敲黑板!敲黑板!
雖然Object.freeze
在必定程度上可以幫助咱們提高一部分的數據性能,可是在使用的時候仍然須要謹慎使用。避免形成數據沒法響應的問題。若是使用Object.freeze
這個屬性再次給其對象屬性賦值時,則會拋出錯誤不能分配給對象的只讀屬性*
。
用這種方法去提高性能若是數據量小的狀況是沒法感受出來的。只有數據量大的時候,纔會感受到數據的明顯變化。
渲染前處理
在渲染數據的時候,後端所返回的數據和UI
設計圖中所須要的數據格式不一致,好比:列表中須要展現一個時間,可是後端返回的是一個時間戳,那麼前端就須要對這部分數據進行處理。通常來講處理這種狀況有一些辦法,使用函數,使用filter
,還有就是在渲染以前把數據處理好。
筆者這裏比較建議在渲染以前把全部的數據處理好,爲何?數據渲染以後完成以後纔會去執行裏面的函數或者是過濾器,這樣會給頁面渲染形成很明顯的額外的負擔。若是對Vue3.0
瞭解的同窗能夠知道,在Vue3.0
中已經把filter
這個功能已經去掉了,推薦使用computed
來實現相同相同的效果。
猜想內容:可能尤大大也發現了filter
給頁面渲染帶來的額外的負擔,並無對頁面的性能提高起到很大的做用。
functional
不是不少函數組件都須要方法,Vue
中爲了表示一個模板應該被編譯成一個功能組件,在模板中 添加了functional
屬性。若是項目中的所使用的組件不是有狀態的組件,那麼就可使用functional
屬性把這個組件抓換成功能組件。
功能組件(不要與Vue的render函數混淆)是一個不包含狀態和實例的組件。功能組件是一個沒有狀態或實例的組件。因爲功能組件沒有狀態,由於不須要爲Vue
的數據響應之類的東西作初始化動做。功能組件仍然會像出入的props
同樣對數據更新作出響應,可是功能組件的自身,因爲它不維護本身的狀態,同時也所以沒法知道本身的數據是否已經發生了改變。在大型項目中使用功能組件之後,在對於DOM
渲染有重大的改進。
因爲功能組件沒有狀態,所以不須要爲Vue的反應系統之類的東西進行額外的初始化。功能組件仍然會像傳入的新道具那樣對更改作出反應,可是在組件自己內,因爲它不維護本身的狀態,所以沒法知道什麼時候數據已更改。
在許多狀況下,功能組件可能不合適。畢竟,使用JavaScript
框架的目的是構建更具反應性的應用程序。在Vue
中,若是沒有適當的反應系統,則沒法執行此操做。
假設咱們的組件接受一個prop.user
,該對象是帶有firstName
和的對象lastName
,而且咱們想要呈現一個顯示用戶全名的模板。在功能<template>
組件中,咱們能夠經過在組件定義上提供一個方法,而後使用$optionsVue
提供的屬性來訪問咱們的特殊方法來作到這一點:
<template functional> <div>{{ $options.userFullName(props.user) }}</div> </template> <script> export default { props: { user: Object }, userFullName(user) { return `${user.firstName} ${user.lastName}` } } </script>
子組件中處理業務
頁面中也會有不少的列表,列表中也會有各類各樣的複雜的狀況,這個時候能夠把一些比較繁重的業務處理存放到其子組件中。
代碼對比:
<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>
優化後:
<template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { /* 長任務在子組件裏。 */ } }, render (h) { return h('div', this.heavy()) } } } } </script>
當組件隨着props:number
的變化,組件patch
從新渲染的時候,heavy
長任務也會從新執行。可是若是能將沒有與父組件相互依賴的元素,拆成一個組件,在父組件須要從新渲染的時候,由於與父組件沒有依賴子組件並不會跟着從新渲染,響應的性能也能獲得提高。
局部做用域
開發過程當中會常用到一些計算屬性或者Util
函數,若是咱們在循環過程當中,不斷的使用this.***
去調用一個計算屬性的時候,每次調用這個值計算屬性都會計算一次,然而這個值確是一個固定不變的值,就形成了很大的性能的浪費。
若是當咱們使用這些屬性的時候,最好的方式是把對應的值取出來,而後再去使用。
<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>
總結
以上是我經過調查資料以及我的項目中的一些小經驗得出的對於Vue
性能優化的一些方案,可能文章中一些看法存在一些問題,歡迎你們在評論區指出,你們一塊兒學習,一塊兒進步。