每個Vue實例在被創建的時候,都會經歷一系列初始化的過程。比如說需要設置數據監聽、模板編譯、將實例掛載到DOM結構上並且在數據變化時對DOM結構進行更新等等。Vue允許開發者在不同的生命周期運行一些鈎子函數(hook),給開發者在不同的生命周期中添加自己代碼的機會。所有的生命周期鈎子自動綁定 this
上下文到實例中,因此你可以訪問數據,對屬性和方法進行運算,這也意味着我們不能夠用箭頭函數來定義生命周期函數。ok,生命周期的定義就到這里,下面看一下vue具體有哪些生命周期,以及再不同的生命周期里面會發生什么事情。
官方給的生命周期鈎子函數如下:
#beforeCreate
#created
#beforeMount
#mounted
#beforeUpdate
#updated
#beforeDestroy
#destroyed
#errorCaptured
#activated
#deactivated
在這里鈎子函數里面,着重看前八個,后面三個在實際開發中基本涉及不到,如果有興趣了解可移步到官方文檔https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90。
1 var vm = { 2 el: '#app', 3 data: { 4 message: 'hello world.' 5 }, 6 beforeCreate: function() { 7 // 在實例初始化之后,數據觀測 (data observer) 和 event/watcher 事件配置之前被調用。 8 // 在這個生命周期里,vue實例還只是空殼,數據和dom結構都還沒有加載 9 console.log(this.message); // undefined 10 }, 11 created: function() { 12 // 在實例創建完成后被立即調用。在這一步,實例已完成以下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el 目前不可見。 13 // 在這個生命周期里,數據已經加載完成,並且在這里面修改數據並不會出發update函數。 14 // 一般情況下會在這里面做一些異步數據的獲取,比如說從服務器獲取數據到本地。 15 console.log(this.message); // 'hello world' 16 }, 17 beforeMount: function() { 18 // 在掛載開始之前被調用:相關的 render 函數首次被調用。 19 // 在這里面,虛擬DOM已經加載完畢,但是對應的真實DOM還未加載,在這里面是獲取不到真實DOM結點的。 20 // 接下來執行render,加載真實DOM。 21 // 在這里面進行數據修改依舊不會觸發update函數。 22 }, 23 mounted: function() { 24 // 在這里面,真實DOM、數據、事件處理已經加載完畢,可以進行對應的修改。 25 // 從這里開始進行數據修改都會觸發update函數。 26 }, 27 beforeUpdate: function() { 28 // 數據更新時調用,發生在虛擬 DOM 打補丁之前。這里適合在更新之前訪問現有的 DOM,比如手動移除已添加的事件監聽器。 29 // 這個沒啥好說的,在數據修改(或者說要重新進行渲染)的時候會觸發的鈎子函數。 30 // 值得注意的是,千萬不要在這里面進行數據修改,否則會陷入死循環!道理你自己思考一下就知道為什么了! 31 32 }, 33 updated: function() { 34 // 由於數據更改導致的虛擬 DOM 重新渲染和打補丁,在這之后會調用該鈎子。 35 // 當這個鈎子被調用時,組件 DOM 已經更新,所以你現在可以執行依賴於 DOM 的操作。然而在大多數情況下,你應該避免在此期間更改狀態。如果要相應狀態改變,通常最好使用計算屬性或 watcher 取而代之。 36 // 同樣的,在數據修改完畢之后會調用的鈎子函數。 37 // 在這里面一樣不能進行數據修改!!!謹記!!! 38 39 }, 40 beforeDestroy: function() { 41 // 實例銷毀之前調用。在這一步,實例仍然完全可用。 42 // 銷毀前執行(手動使用$destroy方法被調用的時候就會執行),一般在這里善后:清除計時器、清除非指令綁定的事件等等。 43 }, 44 destroyed: function() { 45 // Vue 實例銷毀后調用。調用后,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。 46 // 組件的數據綁定、監聽...都去掉了,只剩下dom空殼,這里也可以善后。 47 } 48 }
上面我們說到,在發生數據修改的時候都會觸發beforeUpdate和updated這兩個鈎子函數,但是如果要對特定的修改執行update函數就會變得很麻煩。官方為此提供了另外一種解決辦法,那就是$nextTick。$nextTick()是在下次 DOM 更新循環結束之后執行延遲回調,可以在修改數據之后立即使用這個方法,獲取更新后的 DOM。簡單的理解是:當數據更新了,在dom中渲染后,自動執行該函數。下面舉個簡單的例子:
1 <template> 2 <div> 3 <span ref="span">{{ message }}</span> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name: 'test', 10 data () { 11 return { 12 message: "初始值" 13 } 14 }, 15 methods:{ 16 changeMsg: function(){ 17 this.message = "修改后的值"; 18 console.log(this.$refs.span.innerText); //原始值 19 } 20 }, 21 created() { 22 this.changeMsg(); 23 } 24 } 25 </script>
這是因為在修改完message之后,尚未執行update函數,對應的DOM結構還未發生改變。使用nextTick稍作修改:
1 <template> 2 <div> 3 <span ref="span">{{ message }}</span> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name: 'test', 10 data () { 11 return { 12 message: "初始值" 13 } 14 }, 15 methods:{ 16 changeMsg: function(){ 17 this.message = "修改后的值"; 18 this.$nextTick(function(){ 19 console.log(that.$refs.span.innerText); //修改后的值 20 }); 21 22 } 23 }, 24 created() { 25 this.changeMsg(); 26 } 27 } 28 </script> 29
因此,在某些有特殊需求的情況下,是可以使用nextTick來代替update進行使用的。那么什么情況下需要使用到nextTick()呢?
一、需要在created()鈎子函數進行的DOM操作。因為在created()鈎子函數執行時,真實DOM 並未進行任何渲染,而此時進行DOM操作無異於徒勞,所以此處一定要將DOM操作的js代碼放進nextTick()的回調函數中。與之對應的就是mounted鈎子函數,因為該鈎子函數執行時所有的DOM掛載已完成。
二、需要在改變DOM之后基於新的DOM做點什么,對新DOM一系列的js操作都需要放進nextTick()的回調函數中。
Vue.nextTick(callback)的原理:
Vue是異步執行dom更新的,一旦觀察到數據變化,Vue就會開啟一個隊列,然后把在同一個事件循環 (event loop) 當中觀察到數據變化的 watcher 推送進這個隊列。如果這個watcher被觸發多次,只會被推送到隊列一次。這種緩沖行為可以有效的去掉重復數據造成的不必要的計算和DOM操作。而在下一個事件循環時,Vue會清空隊列,並進行必要的DOM更新。
當你設置 vm.data = 'new value',DOM 並不會馬上更新,而是在異步隊列被清除,也就是下一個事件循環開始時執行更新時才會進行必要的DOM更新。如果此時你想要根據更新的 DOM 狀態去做某些事情,就會出現問題。為了在數據變化之后等待 Vue 完成更新 DOM ,可以在數據變化之后立即使用 Vue.nextTick(callback) 。這樣回調函數在 DOM 更新完成后就會調用。