一、簡介 先貼一下官網對生命周期/鉤子函數的說明(先貼為敬):所有的生命周期鉤子自動綁定 this 上下文到實例中,因此你可以訪問數據,對屬性和方法進行運算。這意味著你不能使用箭頭函數來定義一個生命周期方法 (例如 created: () => this.fetchTodos())。這是因為箭頭函數 ...
一、簡介
先貼一下官網對生命周期/鉤子函數的說明(先貼為敬):所有的生命周期鉤子自動綁定 this
上下文到實例中,因此你可以訪問數據,對屬性和方法進行運算。這意味著你不能使用箭頭函數來定義一個生命周期方法 (例如 created: () => this.fetchTodos()
)。這是因為箭頭函數綁定了父上下文,因此 this
與你期待的 Vue 實例不同,this.fetchTodos
的行為未定義。
上面是官方文檔對生命周期/鉤子函數的總覽介紹。如果單看這個總覽介紹,絕對是一頭霧水,不清不楚看不出個所以然。雖然文檔後面對各個鉤子函數的使用有具體說明,但具體實例卻不是很清楚,所以在玩了一段時間的Vue項目後,閑來打算自己總結下生命周期和鉤子函數的使用。下麵先來一張官方生命周期圖示:
生命周期:描述Vue實例或組件從創建到銷毀(包括銷毀前和銷毀)的全部經歷和過程。就像人一樣,從母親懷胎開始,然後出生,成長,衰老,一直到迴光返照(銷毀前),最後死去一把火(銷毀)回歸大自然,著重是介紹一種經歷和過程。
鉤子函數:鉤子函數則是Vue實例或組件在生命周期過程中各個階段自執行的回調函數。就如同新生兒出生後,餓了他會哭,上學途中被高年級學生欺負了會找家長告狀,長大了要出去掙錢養家,老了會戴老花鏡一樣。在不同的階段Vue實例或組件內部,結構也在發生著變化,隨著節點結構的變化就需要執行一些特定的鉤子函數,去繼續下一步變化和新節點的建立,也正是這些鉤子函數的執行為實際開發過程中能夠添加自定義功能提供了入口。
下麵結合官方文檔先對各個鉤子函數做一個簡略的總結。
二、代碼實測
各個鉤子函數的執行位置以及執行時間點,在上面的官方生命周期圖示中已經標註得很清楚,下麵通過代碼實測來逐個加深認識。測試代碼如下:
<!DOCTYPE html> <html> <head> <title>Vue – 基礎學習(1):對生命周期和鉤子函的理解</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div id="app"> <div>靜態元素</div> <div style="margin-top: 5px">{{ testInfor }}</div> <div style="margin-top: 20px"> <button @click.stop="editTestInfor">更新內容</button> <button @click.stop="destroyedNode">銷毀實例</button> </div> </div> <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.5.20/vue.min.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data() { return { testInfor: '測試信息!', }; }, beforeCreate() { console.group('beforeCreate:實例創建完成,但數據對象data、屬性、event/watcher事件均未完成配置和初始化。掛載階段還未開始,$el屬性未初始化,$el元素不可見========》'); console.log(this); // object console.log('%c%s', 'color:red', 'el : ' + this.$el); // undefined console.log(this.$el); // undefined console.log('%c%s', 'color:red', 'data : ' + this.$data); // undefined console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // undefined this.testFuntion('beforeCreate'); // undefined this.testFuntion is not a function debugger; }, created() { console.group('created:實例數據對象data、屬性、event/watcher事件均配置和初始化完成。但掛載階段還未開始,$el屬性未初始化,$el元素不可見=========================》'); console.log(this); // object console.log('%c%s', 'color:red', 'el : ' + this.$el); // undefined console.log(this.$el); // undefined console.log('%c%s', 'color:red', 'data : ' + this.$data); // 初始化完成 console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 初始化完成 this.testFuntion('created'); // event事件初始化完成 debugger; }, beforeMount() { console.group('beforeMount:在開始掛載之前被調用,相關的render函數首次被調用。$el屬性初始化完成,但處於虛擬dom狀態,具體的data.filter尚未替換,$el元素可見=======》'); console.log(this); // object console.log('%c%s', 'color:red', 'el : ' + this.$el); // $el屬性初始化完成 console.log(this.$el); // 節點掛載完成,但數據尚未渲染,處於虛擬DOM狀態 console.log('%c%s', 'color:red', 'data : ' + this.$data); // 已被初始化 console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 已被初始化 this.testFuntion('beforeMount'); // event事件已被初始化 debugger; }, mounted() { console.group('mounted:掛載完成,data.filter成功渲染,頁面整體渲染完成,可進行DOM操作===================》'); console.log(this); // object console.log('%c%s', 'color:red', 'el : ' + this.$el); // $el屬性已被初始化 console.log(this.$el); // 節點掛載完成,數據渲染成功,頁面全部渲染完成 console.log('%c%s', 'color:red', 'data : ' + this.$data); // 已被初始化 console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 已被初始化 this.testFuntion('mounted'); // event事件已被初始化 debugger; }, beforeUpdate() { console.group('beforeUpdate:頁面依賴的參數數據更改之後,DOM結構重新渲染之前觸發執行(此時DOM結構還沒有重新渲染)=============》'); console.log(this.$el); console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 點擊按鈕調用方法更新後的數據 this.testFuntion('beforeUpdate'); this.testInfor = 'beforeUpdate修改後的信息!'; // 在beforeUpdate函數內再次修改頁面依賴參數數據 console.log('%c%s', 'color:blue', 'data : ' + this.testInfor); // 在beforeUpdate函數內修改後的數據 debugger; }, updated() { console.group('updated:數據更新完成=====================================================================》'); console.log(this.$el); console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 最終更新後數據 this.testFuntion('updated'); debugger; }, beforeDestroy() { console.group('beforeDestroy:實例或組件銷毀之前調用,在這一步,實例或組件仍然完全可用===================》'); console.log(this.$el); console.log('%c%s', 'color:red', 'data : ' + this.testInfor); this.testFuntion('beforeDestroy'); // 此時實例內功能函數功能依然正常 this.testInfor = 'beforeDestroy修改後的信息!'; // 在beforeDestroy函數內再次修改頁面依賴參數數據,用以驗證beforeUpdate和updated函數是否還監聽執行 console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 在beforeDestroy函數內修改後的數據 debugger; // 此時實例、組件雖然頁面結構完整,各種功能正常。但,頁面依賴參數更新後生命周期函數beforeUpdate和updated均不再執行,說明實例或組件的銷毀一旦啟動則不可逆轉或中途打斷。 }, destroyed() { console.group('destroyed:實例或組件已被銷毀=============================================================》'); console.log(this.$el); console.log('%c%s', 'color:red', 'data : ' + this.testInfor); // 在beforeDestroy函數內修改的頁面依賴參數,依然能正確讀取 this.testFuntion('destroyed'); // 此時實例內功能函數功能依然正常 debugger; // 此時雖然"beforeDestroy"執行完畢,但實例指向的所有東西(參數,方法等)尚未解綁。所以此時實例內各參數、方法功能依然正常。等待"destroyed"執行完畢後,所有的東西才會解綁,塵歸塵,土歸土。 }, methods: { testFuntion(type) { console.log('當前運行鉤子函數:' + type); }, editTestInfor() { this.testInfor = '修改後的信息!'; }, destroyedNode() { this.$destroy(); } } }); </script> </body> </html>
1. beforeCreate 和created
beforeCreate:實例創建完成,但數據對象data、屬性、event/watcher事件均未完成配置和初始化。掛載階段未開始,$el屬性尚未初始化,$el屬性不可見,$el元素不可見。
created:實例數據對象data、屬性、event/watcher事件均配置和初始化完成。但掛載階段尚未開始,$el屬性未初始化,$el屬性不可見,$el元素不可見。
小結:雖然此時$el屬性尚未初始化,頁面元素不可見,但數據對象data、屬性、event/watcher事件均已配置和初始化完成,所以一些需要先頁面執行的方法(如ajax請求,頁面功能許可權檢測(頁面是否能載入、頁面依賴參數是否合法)和配置(如按鈕點擊許可權等))在created階段可以執行,但不允許操作DOM節點和調用操作DOM節點的方法(頁面整體結構未渲染完成)。
2. beforeMount和mounted
beforeMount:在開始掛載之前被調用,相關的render函數首次被調用。$el屬性初始化完成,$el屬性可見,el元素可見。但此時el節點並沒有渲染進數據,el節點尚處於“虛擬”節點狀態,可看到還是取值表達式{{testInfor }}。這就是Virtual DOM(虛擬Dom)的巧妙之處,先占坑,然後到mounted掛載階段時再渲染值。
mounted :節點掛載完成,數據成功渲染,頁面整體渲染完成,實例或組件完全成熟,可進行DOM操作。
3. beforeDestroy 和 destroy
人生看似很漫長,但在不經意之間就走向了她的終點。Vue實例或組件也一樣,在經歷了多姿多彩的絢爛時光後,它也逐漸走向了它生命的終點。這裡提一下為啥先不說 beforeUpdate 和 updated 而是直接跳到 beforeDestroy 和 destroy,因為 beforeUpdate 和 updated 不是生命周期過程中必須執行的鉤子函數。beforeUpdate 和 updated 是基於組件內數據發生變化時觸發執行,如果當期實例或組件內數據只是進行顯示,不進行任何修改,那麼這兩個鉤子函數將一直不會被觸發,也就不會被執行。
beforeDestroy:實例或組件銷毀之前調用,在這一步,實例或組件內各參數,方法功能依然完整,實例仍完全可用。
destroy:Vue實例或組件銷毀後調用。調用後,Vue 實例指示的所有東西都會自動解綁,所有的事件監聽器會被移除,所有的子實例也會被銷毀。
實例或組件銷毀完成後,再次點擊“更新內容”按鈕,此時系統不再做任何響應,但,已渲染完成的Dom結構和節點元素依然存在,所以當執行完destroy操作後,實例或組件就不再受Vue系統控制。此時Vue實例或該子組件已經不存在了,實例或組件內的各參數,屬性,方法均已被記憶體回收清空。
小結:beforeDestroy階段,此時實例、組件雖然頁面結構完整,各種功能正常,但,頁面依賴參數更新後生命周期函數beforeUpdate和updated均不再執行,說明實例或組件的銷毀過程一旦啟動則不可逆轉或中途打斷。
destroy階段,此時雖然"beforeDestroy"執行完畢,但實例指向的所有東西(參數,方法等)尚未解綁。所以此時實例內各參數、方法功能依然正常。等待"destroyed"執行完畢後,所有的東西才會被解綁,資源被回收。塵歸塵,土歸土,從哪來回哪去!
到此為止,Vue實例或組件從開始初始化到最終銷毀,數據清空的六個鉤子函數均測試完畢。這六個鉤子函數是實例或組件生命周期歷程中最主要的六個鉤子函數,也是必須執行的六個函數,無法繞過。
4. beforeUpdate 和 updated
現在,返回來看beforeUpdate 和 updated。Vue實例或組件在掛載完成後就標志著功能健全,功能健全的組件就如成年的人生一樣豐富多彩,每時每刻都可能發生變化。接下來通過修改testInfor的值,來看看beforeUpdate和updated都各自做了什麼。
點擊頁面“更新內容”按鈕,修改testInfor的值。
beforeUpdate:頁面依賴的參數數據更改之後,虛擬DOM重新渲染和打補丁之前執行(此時DOM結構還未重新渲染)。
updated:頁面依賴的參數數據更改之後,beforeUpdate鉤子函數執行完畢,會立即進行DOM結構的重新渲染。DOM結構渲染完成之後才會調用updated鉤子函數,而不是渲染時就調用。
此圖就可以完全看出,調用updated時組件DOM結構已重新渲染完成,所以此時updated函數內是可以進行相關DOM操作的。
小結:在debugger beforeUpdate鉤子函數時發現一個小細節,既然beforeUpdate是在頁面依賴數據修改之後,虛擬DOM重新渲染之前執行,那麼我在beforeUpdate函數內,是可以對依賴數據進行再次修改的,而不會導致多重渲染,也不會多次調用updated函數。
從上兩圖可以看出,即使在beforeUpdate函數內修改無數次頁面依賴參數數據,組件Dom結構也只會重新渲染一次,即 將最後修改的依賴參數數據渲染到對應節點,updated函數也只會執行一次。只是這樣做沒有多大實際意義,畢竟其他地方調用其他方法更新後的數據,是頁面功能需求的數據,在beforeUpdate這又瞎改一通,於功能於系統毫無益處。當然你膽肥不怕死,整一些惡搞和亂操作還是可以玩的。
雖然beforeUpdate和updated是基於頁面依賴參數數據更改後觸發和執行,對於頁面依賴參數的變化可以起到監控作用,以及在參數變化之後執行其他後續操作,但,它們無法判定是哪個參數發生了變化。雖然每次參數數據變化之後可以通過比較各個參數值的前後值是否相等來判定是哪個參數發生了變化,但,那是基於參數量少,參數數據類型是基本數據類型的情況。一旦需要監控的參數量大,參數數據類型複雜,beforeUpdate和updated就將變得很難處理。所以實際開發過程中,除非一些特別的參數和操作,絕大部分參數的更新監聽和後續操作,都是使用watch對象進行監聽,因而在實際開發過程中beforeUpdate和updated使用得相當少。
另外Vue是數據驅動頁面刷新,所以必然是在數據更新之後系統才會驅動虛擬DOM View層的刷新,因而beforeUpdate必然是在參數數據更新之後,View(視圖)層數據(節點內數據)更新之前觸發。
三、總結
總體而言,生命周期函數雖然有這麼多個,但實際開發過程中使用最頻繁的也就那麼幾個,如:created,mounted,beforeDestory,destoryed。開發人員可以:
在created內進行:頁面是否載入 許可權判定或頁面依賴參數初始化(如按鈕許可權配置)、ajax數據請求、自執行函數調用等操作。
在mounted內進行:數據過濾、數據渲染賦值(如下拉框選項賦值)、DOM節點操作等功能。
在beforeDestroy內進行:參數判定,確定當前頁面是否允許切換或刷新、必要數據緩存,操作記錄上傳等操作。
在destoryed內進行:清除當前頁面其他緩存數據,如sessionStorage、定時器等。
而其他生命周期函數,並不是說它們就不重要,只是它們在平常的開發過程中,使用得不是那麼頻繁而已。它們也有它們自己獨特的用處和用法,所以對於生命周期和生命周期函數的善加利用,可以讓實際開發事半功倍,並收到良好的效果。