vue nextTick深入理解---vue效能優化、DOM更新時機、事件迴圈機制
定義[nextTick、事件迴圈]
nextTick的由來:
由於vue的資料驅動檢視更新是非同步的,即修改資料的當下,檢視不會立即更新,而是等同一事件迴圈中的所有資料變化完成之後再統一進行檢視更新。
nextTick的觸發時機:
在同一事件迴圈中的資料變化後,DOM完成更新,立即執行nextTick(callback)內的回撥
應用場景:
需要在檢視更新之後,基於新的檢視進行操作。
事件迴圈:
以上出現了事件迴圈的概念,其涉及到JS的執行機制,包括主執行緒的執行棧、非同步佇列、非同步API、事件迴圈的協作。大致理解:主執行緒完成同步環境執行,查詢任務佇列、提取隊首任務、放入主執行緒中執行;執行完畢再重複該操作,該過程稱為事件迴圈。而主執行緒的每次讀取任務佇列操作,是一個事件迴圈的開始。非同步callback不可能處於同一事件迴圈中。 簡單總結事件迴圈: 同步程式碼執行->查詢非同步佇列、推入執行棧、執行callback1[事件迴圈1]->查詢非同步佇列、推入執行棧。執行callback2[事件迴圈2]... 即每個非同步callback最終都會形成自己獨立的一個事件迴圈。 結合nextTick的由來,可以推出每個事件迴圈中,nextTick的觸發時機:同一事件迴圈中的程式碼執行完畢->DOM更新->nextTick callback觸發
例項理解nextTick的使用,並給出在頁面渲染上的優化巧用
tips:程式碼的正確閱讀方式 看template組成、跳過script程式碼、看程式碼後面的用例設計、看之後的程式碼分析、同時回頭結合script程式碼理解
<template>
<div>
<ul>
<li v-for="item in list1">{{item}}</li>
</ul>
<ul>
<li v-for="item in list2" >{{item}}</li>
</ul>
<ol>
<li v-for="item in list3">{{item}}</li>
</ol>
<ol>
<li v-for="item in list4">{{item}}</li>
</ol>
<ol>
<li v-for="item in list5" >{{item}}</li>
</ol>
</div>
</template>
<script type="text/javascript">
export default {
data() {
return {
list1: [],
list2: [],
list3: [],
list4: [],
list5: []
}
},
created() {
this.composeList12()
this.composeList34()
this.composeList5()
this.$nextTick(function() {
// DOM 更新了
console.log('finished test ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
methods:{
composeList12(){
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list1, i, 'I am a 測試資訊~~啦啦啦' + i)
}
console.log('finished list1 ' + new Date().toString())
for (let i = 0; i < count; i++) {
Vue.set(me.list2, i, 'I am a 測試資訊~~啦啦啦' + i)
}
console.log('finished list2 ' + new Date().toString())
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick1&2 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
composeList34() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list3, i, 'I am a 測試資訊~~啦啦啦' + i)
}
console.log('finished list3 ' + new Date().toString())
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick3 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
setTimeout(me.setTimeout1, 0)
},
setTimeout1() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list4, i, 'I am a 測試資訊~~啦啦啦' + i)
}
console.log('finished list4 ' + new Date().toString())
me.$nextTick(function() {
// DOM 更新了
console.log('finished tick4 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
composeList5() {
let me = this
let count = 10000
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick5-1 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
setTimeout(me.setTimeout2, 0)
},
setTimeout2() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list5, i, 'I am a 測試資訊~~啦啦啦' + i)
}
console.log('finished list5 ' + new Date().toString())
me.$nextTick(function() {
// DOM 更新了
console.log('finished tick5 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
}
}
}
</script>
2.1、用例設計
用例1:通過list1、2、3驗證,處在同步程式碼中的DOM更新情況及nextTick的觸發時機;
用例2:通過list3、list4驗證,同步程式碼及非同步程式碼中Dom更新及nextTick觸發的區別;
用例3:通過list4、list5對比驗證,多個非同步程式碼中nextTick觸發的區別;
用例4:通過在檢視更新後獲取DOM中li的數量,判斷nextTick序列渲染的時間點。
2.2、程式碼分析
函式執行步驟:
事件迴圈1:
step1: this.composeList12() -> update list1, update list2 -> 繫結tick’1&2’
step2: this.composeList34() -> update list3, 設定非同步1setTimeout1 -> 繫結tick’3’
step3: this.composeList5() -> 繫結tick’5-1’ -> 設定非同步2setTimeout2
step4: 繫結tick’test’
事件迴圈2:
將setTimeout1的callback推入執行棧 -> update list4 -> 繫結tick’4’
事件迴圈3:
將setTimeout2的callback推入執行棧 -> update list5 -> 繫結tick’5’
2.3、推斷輸出訊息
由於同一事件迴圈中的tick按執行順序,因此訊息輸出為即:
[同步環境]update list1 -> update list2 -> update list3 -> tick‘1&2’ -> tick‘3’ -> tick’5-1’ -> tick’test’
[事件迴圈1]->update list4 -> tick’4’
[事件迴圈2] ->update list5 -> tick’5’
2.4、實際執行結果如下圖
該demo中設定了5個size為10000的陣列,從而能從時間及訊息輸出兩個維度來了解nextTick的執行情況。另外,額外增加了一個引數,即更新後的檢視中的li數量,從這個數量可以考察出同一事件迴圈中的nextTick執行情況。由執行結果圖可以看出實際的輸出與推導的輸出結果相符合。
2.5、總結
從用例1得出:
1、在同一事件迴圈中,只有所有的資料更新完畢,才會呼叫nextTick
2、 僅在同步執行環境資料更新完畢,DOM才開始渲染,頁面才開始展現。
3、在同一事件迴圈中,如果存在多個nextTick,將會按照最初的執行順序呼叫。
從用例1-4得出:
1、從同步執行環境中的四個tick對應的li數量均為30000可看出,同一事件迴圈中,nextTick所在的檢視是相同的。
從用例2得出:
1、只有同步環境執行完畢、DOM渲染完畢,才會處理非同步callback
從用例3得出:
1、每個非同步callback最後都會處在一個獨立的事件迴圈中,對應自己獨立的nextTick
巧用:
從用例1結論中可得出:
這個事件環境中的資料變化完成,再進行渲染[檢視更新],可以避免DOM的頻繁變動,從而避免了因此帶來的瀏覽器卡頓,大幅度提升效能。
在首屏渲染、使用者互動的過程中,要巧用同步環境及非同步環境;首屏展現的內容,儘量保證在同步環境中完成;其他內容,拆分到非同步中,從而保證效能、體驗。
tips:
可產生非同步callback的有:promise(microtask queue)、setTimeout、MutationObserver、DOM事件、Ajax等
vue DOM的檢視更新實現,使用到了ES6的Promise及HTML5的MutationObserver,當環境不支援時,使用setTimeout(fn,0)替代。上述的三種方法,均為非同步API。其中MutationObserver類似事件,又有所區別;事件時同步觸發的,其為非同步觸發,即DOM發生變化之後,不會立刻觸發,等當前所有的DOM操作都結束後觸發。