
導語 | Vue是一套用于構建用戶界面的漸進式的JavaScript框架,它具有體積小,更高的運行效率,雙向資料系結,生態豐富、學習成本低等優點,所以Vue也被廣泛用在移動端跨平臺框架上,接下來,我將為大家梳理10個實作Vue.js極致性能優化的技巧,以供大家在實際運用中使用,
Vue框架通過資料雙向系結和虛擬DOM技術,幫我們處理了前端開發中最臟最累的DOM操作部分,我們不再需要去考慮如何操作DOM以及如何最高效地操作DOM,但是我們仍然需要去關注Vue在跨平臺專案性能方面的優化,使專案具有更高效的性能、更好的用戶體驗,
一、v-for遍歷必須為item添加key,
且避免同時使用v-if
在串列資料進行遍歷渲染時,需要為每一項item設定唯一key值,方便Vue.js內部機制精準找到該條串列資料,當state更新時,新的狀態值和舊的狀態值對比,較快地定位到diff,
我們在使用的使用經常會使用index(即陣列的下標)來作為key,但其實這是不推薦的一種使用方法,
舉個例子:
var list = [
{
id: 1,
name: 'test1',
},
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
]
<div v-for="(item, index) in list" :key="index" >{{item.name}}</div>
在最后一條資料后再加一條資料:
var list = [
{
id: 1,
name: 'test1',
},
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
{
id: 4,
name: '我是在最后添加的一條資料',
},
]
此時前三條資料直接復用之前的,新渲染最后一條資料,此時用index作為key,沒有任何問題,
在中間插入一條資料:
var list = [
{
id: 1,
name: 'test1',
},
{
id: 4,
name: '我是插隊的那條資料',
},
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
]
此時更新渲染資料,通過index定義的key去進行前后資料的對比,發現:
之前的資料 之后的資料
key: 0 index: 0 name: test1 key: 0 index: 0 name: test1
key: 1 index: 1 name: test2 key: 1 index: 1 name: 我是插隊的那條資料
key: 2 index: 2 name: test3 key: 2 index: 2 name: test2
key: 3 index: 3 name: test3
通過上面清晰的對比,發現除了第一個資料可以復用之前的之外,另外三條資料都需要重新渲染,
是不是很驚奇,我明明只是插入了一條資料,怎么三條資料都要重新渲染?而我想要的只是新增的那一條資料新渲染出來就行了,
最好的辦法是使用陣列中不會變化的那一項作為key值,對應到專案中,即每條資料都有一個唯一的id,來標識這條資料的唯一性;使用id作為key值,我們再來對比一下向中間插入一條資料,此時會怎么去渲染,
之前的資料 之后的資料
key: 1 id: 1 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1
key: 2 id: 2 index: 1 name: test2 key: 4 id: 4 index: 1 name: 我是插隊的那條資料
key: 3 id: 3 index: 2 name: test3 key: 2 id: 2 index: 2 name: test2
key: 3 id: 3 index: 3 name: test3
現在對比發現只有一條資料變化了,就是id為4的那條資料,因此只要新渲染這一條資料就可以了,其他都是就復用之前的,
總結:所以一句話,key的作用主要是為了高效的更新虛擬DOM,另外Vue中在使用相同標簽名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓Vue可以區分它們,否則Vue只會替換其內部屬性而不會觸發過渡效果,
v-for遍歷避免同時使用v-if,v-for比v-if優先級高,如果每一次都需要遍歷整個陣列,將會影響速度,尤其是當之需要渲染很小一部分的時候,必要情況下應該替換成computed屬性,
二、長串列性能優化
Vue會通過Object.defineProperty對資料進行劫持,來實作視圖回應資料的變化,然而有些時候我們的組件就是純粹的資料展示,不會有任何改變,我們就不需要Vue來劫持我們的資料,在大量資料展示的情況下,這能夠很明顯的減少組件初始化的時間,那如何禁止Vue劫持我們的資料呢?可以通過Object.freeze方法來凍結一個物件,一旦被凍結的物件就再也不能被修改了,
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
三、Vue組件中的data是函式而不是物件
export default {
data() {
// data是一個函式,data: function() {}的簡寫
return {
// 頁面要初始化的資料
name: 'bartonwang',
};
},
};
而非如下所示:
export default {
data: {
// data是一個物件
name: 'bartonwang',
},
};
當一個組件被定義,data必須宣告為回傳一個初始資料物件的函式,因為組件可能被用來創建多個實體,復用在多個頁面,
如果data是一個純碎的物件,則所有的實體將共享參考同一份data資料物件,無論在哪個組件實體中修改data,都會影響到所有的組件實體,
如果data是函式,每次創建一個新實體后,呼叫data函式,從而回傳初始資料的一個全新副本資料物件,
這樣每復用一次組件,會回傳一份新的data資料,類似于給每個組件實體創建一個私有的資料空間,讓各個組件的實體各自獨立,互不影響,保持低耦合,
四、Vue鉤子函式之鉤子事件hookEvent,
監聽組件簡化代碼
用法:
通過$on(eventName, eventHandler) 偵聽一個事件,
通過$once(eventName,eventHandler) 一次性偵聽一個事件,
通過$off(eventName, eventHandler) 停止偵聽一個事件,
通常實作一個定時器的呼叫與銷毀我可能會以以下方式實作:
export default{
data(){
timer:null // 需要創建實體
},
mounted(){
this.timer = setInterval(()=>{
//具體執行內容
console.log('1');
},1000);
}
beforeDestory(){
clearInterval(this.timer);
this.timer = null;
}
}
這種方法存在的問題是:
vue實體中需要有這個定時器的實體,感覺有點多余,創建的定時器代碼和銷毀定時器的代碼沒有放在一起,不容易維護,通常很容易忘記去清理這個定時器,
使用$on(‘hook:’)監聽beforeDestory生命周期可以避免該問題,并且因為只需要監聽一次,所以使用$once進行注冊監聽,
export default{
methods:{
fn(){
const timer = setInterval(()=>{
console.log('1');
},1000);
this.$once('hook:beforeDestory',()=>{ // 監聽一次即可
clearInterval(timer);
timer = null;
})
}
}
}
五、組件懶加載
在單頁應用中,如果沒有應用懶加載,運用webpack打包后的檔案將會例外地大,造成進入首頁時需要加載的內容過多,延時過長,不利于用戶體驗,而運用懶加載則可以將頁面進行劃分,需要的時候加載頁面,可以有效的分擔首頁所承擔的加載壓力,減少首頁加載用時,
Vue.js 2.0組件級懶加載方案:
支持組件可見或即將可見時懶加載
支持組件延時加載
支持加載真實組件前展示骨架組件,提高用戶體驗
支持真實組件代碼分包異步加載
安裝:
npm install@xunlei/vue-lazy-component
在組件中實作區域注冊組件:
import { component as VueLazyComponent } from '@xunlei/vue-lazy-component'
export default {
components: {
'vue-lazy-component': VueLazyComponent
}
}
需要懶加載的組件將其包裹在vue-lazy-component中,slot值為skeleton指的是在懶加載程序中顯示的加載狀態組件,
<vue-lazy-component :timeout="5000" tagName="div">
<child1 slot="skeleton" />
<child2 />
<child3 />
<child4 />
<child5 />
</vue-lazy-component>
六、非回應式資料
初始化時,Vue會對data做getter、setter改造,在Vue的檔案中介紹資料系結和回應時,特意標注了對于經過Object.freeze()方法的物件無法進行更新回應,
性能提升對比
在基于Vue的一個big table benchmark里,可以看到在渲染一個一個1000x10的表格的時候,開啟Object.freeze()前后重新渲染的對比,

開啟優化之前:

開啟優化之后:

在這個例子里,使用了Object.freeze()比不使用快了4倍,
為什么Object.freeze()的性能會更好,不使用Object.freeze()的CPU開銷?

使用Object.freeze()的CPU開銷:

對比可以看出,使用了Object.freeze()之后,減少了observer的開銷,
七、不要將所有的資料都放到data中
data中的資料都會增加getter和setter,又會收集watcher,這樣還占記憶體,不需要回應式的資料我們可以定義在實體上,

八、v-for元素系結事件代理
事件代理作用主要是2個:
將事件處理程式代理到父節點,減少記憶體占用率,
動態生成子節點時能自動系結事件處理程式到父節點,
不使用事件代理,每個span節點系結一個click事件,并指向同一個事件處理程式:
<div>
<span
v-for="(item,index) of 100000"
:key="index"
@click="handleClick">
{{item}}
</span>
</div>
不使用事件代理,每個span節點系結一個click事件,并指向不同的事件處理程式
<div>
<span
v-for="(item,index) of 100000"
:key="index"
@click="function () {}">
{{item}}
</span>
</div>
使用事件代理
<div @click="handleClick">
<span
v-for="(item,index) of 100000"
:key="index">
{{item}}
</span>
</div>

可以看到使用事件代理無論是監聽器數量和記憶體占用率都比前兩者要少,同時對比3個圖中監聽器的數量并沒有發現Vue會自動做事件代理,但是一般給v-for系結事件時,都會讓節點指向同一個事件處理程式(第二種情況可以運行,但是eslint會警告),一定程度上比每生成一個節點都系結一個不同的事件處理程式性能好,但是監聽器的數量仍不會變,所以使用事件代理會更好一點,
代碼使用:
<ul @click="meths">
<li v-for="(item,key) in 10" :key="key" :data-index="key">{{item}}</li>
</ul>
meths(e) {
if (e.target.nodeName.toLowerCase() === 'li') {
console.log(e.target.innerHTML)
console.log(e.target.dataset)
}
}
九、函式式組件
函式式組件是無狀態,它無法實體化,沒有任何的生命周期和方法,創建函式式組件也很簡單,只需要在模板添加functional宣告即可,一般適合只依賴于外部資料的變化而變化的組件,因其輕量,渲染性能也會有所提高,
組件需要的一切都是通過context引數傳遞,它是一個背景關系物件,具體屬性查看檔案,這里props是一個包含所有系結屬性的物件,
函式式組件

十、函式式組件provide和inject組件通信
痛點:常用的父子組件通信方式都是父組件系結要傳遞給子組件的資料,子組件通過props屬性接收,一旦組件層級變多時,采用這種方式一級一級傳遞值非常麻煩,而且代碼可讀性不高,不便后期維護,
Vue提供了provide和inject幫助我們解決多層次嵌套嵌套通信問題,在provide中指定要傳遞給子孫組件的資料,子孫組件通過inject注入祖父組件傳遞過來的資料,可以輕松實作跨級訪問父組件的資料,
provide:是一個物件,或者是一個回傳物件的函式,里面呢就包含要給子孫后代的東西,也就是屬性和屬性值,注意:子孫層的provide會掩蓋祖父層provide中相同key的屬性值,
inject:一個字串陣列,或者是一個物件,屬性值可以是一個物件,包含from和default默認值,from是在可用的注入內容中搜索用的key (字串或Symbol),意思就是祖父多層provide提供了很多資料,from屬性指定取哪一個key;default指定默認值,



從上面這個例子可以看出,只要在父組件中呼叫了,那么在這個父組件生效的生命周期內,所有的子組件都可以呼叫inject來注入父組件中的值,
在使用場景中,肯定是希望父組件的資料一旦發生改變,子孫組件獲取到的也是父組件更新后的資料,那么,怎么實作父組件與子孫組件所系結的資料動態回應呢?
-------------------parent.vue----------------------
provide(){
return {
// keyName: {name:this.name}, // value 是物件才能實作回應式,也就是參考型別
keyName: this.changeValue // 通過函式的方式也可以[注意,這里是把函式作為value,而不是this.changeValue()]
// keyName: 'test' value 如果是基本型別,就無法實作回應式
}
},
data(){
return {
name:'張三'
}
},
methods: {
changeValue(){
this.name = '改變后的名字-李四'
}
}
-------------grandson.vue-----------------
inject:['keyName']
create(){
console.log(this.keyName) // 改變后的名字-李四
}
作者簡介

王雄
騰訊客戶端開發工程師
騰訊客戶端開發工程師,目前在IEG增值服務部從事掌上道聚城app開發作業,有豐富的跨平臺weex,react-native,flutter開發經驗,
推薦閱讀
為什么WebAssembly不是JavaScript的終結者,而是它的“助推器”?
快人一步掌握vue原始碼解讀,搞定diff演算法!(超詳細)
Linux入門必看:如何在60秒內分析Linux性能?
“Docker VS Kubernetes”是共生還是相愛相殺?

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/301542.html
標籤:其他
上一篇:利用CORS解決跨域問題
下一篇:js實作模糊查詢
