

根據 HTML Standard,在每個 task 運行完以后,UI 都會重渲染,那么在 micro task 中就完成數據更新,當前 task 結束就可以得到最新的 UI 了。反之如果新建一個 task 來做數據更新,那么渲染就會進行兩次。
micro task的這一特性是做隊列控制的最佳選擇,vue進行DOM更新內部也是調用nextTick來做異步隊列控制。而當我們自己調用nextTick的時候,它就在更新DOM的那個micro task后追加了我們自己的回調函數,從而確保我們的代碼在DOM更新后執行。
比如一段時間內,你無意中修改了最初代碼片段中的 msg多次,其實只要最后一次修改后的值更新到DOM就可以了,假如是同步更新的,每次 msg 值發生變化,那么都要觸發 setter->Dep->Watcher->update->patch ,這個過程非常消耗性能。
vm.$nextTick( [callback] )
官方解釋:將回調延遲到下次DOM更新循環之后執行。
要理解這句話,首先要了解一下vue的異步更新隊列,vue異步執行dom更新。只要觀察到數據變化,不會立即更新DOM,vue將開啟一個隊列,並緩沖在同一事件循環中發生的所有數據改變。
如果同一個數據被多次改變,只會被推到隊列中一次。例如,當你設置vm.someData = 'new value',對應的dom更新會被推到一個隊列里,該組件不會立即重新渲染,會在當前tick完畢后,在下一個tick中渲染dom。在事件循環中,每進行一次循環操作稱為tick。而nextTick函數就是vue提供的一個實例方法,數據更新后等待下一個tick里dom更新完后執行回調,回調的this自動綁定到調用它的實例上。
例如:
html: <span class="test">{{egData}}</span> <el-button @click="changeData">改變</el-button> js: new Vue({ data () { return { egData: 'old Message' } } methods: { changeData () { this.egData = 'new Message' console.log($('.test').html(), '-----------------------') } } })
結果: 第一次點擊輸出 old Message -----------------------,第二次點擊輸出 new Message -----------------------
使用$nextTick:
js:
new Vue({
data () {
return {
egData: 'old Message'
}
}
methods: {
changeData () {
this.egData = 'new Message'
this.$nextTick(function () {
console.log($('.test').html(), '-----------------------')
})
}
}
})
結果:不管第幾次點擊,都輸出 new Message -----------------------
$nextTick使用場景:
1、數據更新后想要馬上操作新的DOM,需要把操作寫在nextTick的回調里
2、在created鈎子函數里需要操作DOM,也可以把操作寫在nextTick的回調里,(created鈎子函數里還沒有掛載dom,所以直接操作會有問
this.$nextTick()將回調延遲到下次DOM更新循環之后執行。在修改數據之后立即使用它,然后等待DOM更新。它跟全局方法Vue.nextTick一樣,不同的是回調的this自動綁定到調用它的實例上。
nextTick的由來:由於VUE的數據驅動視圖更新,是異步的,即修改數據的當下,視圖不會立刻更新,而是等同一事件中的所有數據變化完成之后,再統一進行視圖更新。
nextTick的觸發時機:在同一事件循環中的數據變化后,DOM完成更新,立即執行nextTick(callback)內的回調。
應用場景:需要在視圖更新之后,基於新的視圖進行操作。
以上出現了事件循環的概念,其涉及到JS的運行機制,包括主線程的執行棧、異步隊列、異步API、時間循環的寫作。大致可以理解為:主線程完成同步環境執行,查詢任務隊列,提取隊首的任務,放入主線程中執行;執行完畢,再重復該操作,該過程稱為事件循環。而主線程的每次讀取任務隊列操作,是一個事件循環的開始。異步callback不可能處在同一事件循環中。
簡單總結事件循環:同步代碼執行→查找異步隊列,推入執行棧,執行callback[事件循環1]→查找異步隊列,推入執行棧,執行callback2[事件循環2]...
即每個異步callback,最終都會形成自己獨立的一個事件循環。
結合nextTick的由來,可以推出每個事件循環中,nextTick觸發的時機:同一事件循環中的代碼執行完畢→DOM更新→nextTick callback觸發。
ps:上文中的任務隊列、消息隊列、異步隊列指向同一個東西,均指macrotask queue。
用法:在下次DOM更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的DOM。
- 在Vue生命周期的
created()鈎子函數進行的DOM操作一定要放在Vue.nextTick()的回調函數中
在created()鈎子函數執行的時候DOM 其實並未進行任何渲染,而此時進行DOM操作無異於徒勞,所以此處一定要將DOM操作的js代碼放進Vue.nextTick()的回調函數中。與之對應的就是mounted()鈎子函數,因為該鈎子函數執行時所有的DOM掛載和渲染都已完成,此時在該鈎子函數中進行任何DOM操作都不會有問題 。
- 在數據變化后要執行的某個操作,而這個操作需要使用隨數據改變而改變的DOM結構的時候,這個操作都應該放進
Vue.nextTick()的回調函數中
Vue.nextTick(callback) 。這樣回調函數在 DOM 更新完成后就會調用。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>nextTick</title> <script src="./js/vue.min.js"></script> </head> <body> <div class="app"> <div ref="msgDiv">{{msg}}</div> <div v-if="msg1">Message got outside 1 $nextTick: {{msg1}}</div> <div v-if="msg2">Message got inside 2 $nextTick: {{msg2}}</div> <div v-if="msg3">Message got outside 3 $nextTick: {{msg3}}</div> <button @click="changeMsg"> Change the Message </button> </div> <script> new Vue({ el: '.app', data: { msg: 'Hello Vue.', msg1: '', msg2: '', msg3: '' }, methods: { changeMsg() { this.msg = "Hello world." this.msg1 = this.$refs.msgDiv.innerHTML this.$nextTick(() => { this.msg2 = this.$refs.msgDiv.innerHTML }) this.msg3 = this.$refs.msgDiv.innerHTML } } }) </script> </body> </html>
msg1和msg3顯示的內容還是變換之前的,而msg2顯示的內容是變換之后的。其根本原因是因為Vue中DOM更新是異步的
需要注意的是,在 created 和 mounted 階段,如果需要操作渲染后的試圖,也要使用 nextTick 方法。
官方文檔說明:
注意 mounted 不會承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用 vm.$nextTick 替換掉 mounted
mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }
https://segmentfault.com/a/1190000012861862
思否參考↑
