Vue – 基礎學習(1):對生命週期和鉤子函的理解

1、簡介

  先貼一下官網對生命週期/鉤子函數的說明(先貼爲敬):全部的生命週期鉤子自動綁定 this 上下文到實例中,所以你能夠訪問數據,對屬性和方法進行運算。這意味着你不能使用箭頭函數來定義一個生命週期方法 (例如 created: () => this.fetchTodos())。這是由於箭頭函數綁定了父上下文,所以 this 與你期待的 Vue 實例不一樣,this.fetchTodos 的行爲未定義。javascript

  上面是官方文檔對生命週期/鉤子函數的總覽介紹。若是單看這個總覽介紹,絕對是一頭霧水,不清不楚看不出個因此然。雖然文檔後面對各個鉤子函數的使用有具體說明,但具體實例卻不是很清楚,因此在玩了一段時間的Vue項目後,閒來打算本身總結下生命週期和鉤子函數的使用。下面先來一張官方生命週期圖示:css

  

  生命週期:描述Vue實例或組件從建立到銷燬(包括銷燬前和銷燬)的所有經歷和過程。就像人同樣,從母親懷胎開始,而後出生,成長,衰老,一直到迴光返照(銷燬前),最後死去一把火(銷燬)迴歸大天然,着重是介紹一種經歷和過程html

  鉤子函數:鉤子函數則是Vue實例或組件在生命週期過程當中各個階段自執行的回調函數。就如同新生兒出生後,餓了他會哭,上學途中被高年級學生欺負了會找家長告狀,長大了要出去掙錢養家,老了會戴老花鏡同樣。在不一樣的階段Vue實例或組件內部,結構也在發生着變化,隨着節點結構的變化就須要執行一些特定的鉤子函數,去繼續下一步變化和新節點的創建,也正是這些鉤子函數的執行爲實際開發過程當中可以添加自定義功能提供了入口。vue

 

  下面結合官方文檔先對各個鉤子函數作一個簡略的總結。java

 

2、代碼實測

   各個鉤子函數的執行位置以及執行時間點,在上面的官方生命週期圖示中已經標註得很清楚,下面經過代碼實測來逐個加深認識。測試代碼以下:ajax

<!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元素不可見。session

     

    小結:雖然此時$el屬性還沒有初始化,頁面元素不可見,但數據對象data、屬性、event/watcher事件均已配置和初始化完成,因此一些須要先頁面執行的方法(如ajax請求,頁面功能權限檢測(頁面是否能加載、頁面依賴參數是否合法)和配置(如按鈕點擊權限等))在created階段能夠執行,但不容許操做DOM節點和調用操做DOM節點的方法(頁面總體結構未渲染完成)。app

 

  2. beforeMount和mounted

    beforeMount:在開始掛載以前被調用,相關的render函數首次被調用。$el屬性初始化完成,$el屬性可見,el元素可見。但此時el節點並無渲染進數據,el節點尚處於「虛擬」節點狀態,可看到仍是取值表達式{{testInfor }}。這就是Virtual DOM(虛擬Dom)的巧妙之處,先佔坑,而後到mounted掛載階段時再渲染值。dom

    

    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(視圖)層數據(節點內數據)更新以前觸發

 

3、總結

  整體而言,生命週期函數雖然有這麼多個,但實際開發過程當中使用最頻繁的也就那麼幾個,如:created,mounted,beforeDestory,destoryed。開發人員能夠:

  在created內進行:頁面是否加載 權限斷定或頁面依賴參數初始化(如按鈕權限配置)、ajax數據請求、自執行函數調用等操做。

  在mounted內進行:數據過濾、數據渲染賦值(以下拉框選項賦值)、DOM節點操做等功能。

  在beforeDestroy內進行:參數斷定,肯定當前頁面是否容許切換或刷新、必要數據緩存,操做記錄上傳等操做。

  在destoryed內進行:清除當前頁面其餘緩存數據,如sessionStorage、定時器等。

  而其餘生命週期函數,並非說它們就不重要,只是它們在日常的開發過程當中,使用得不是那麼頻繁而已。它們也有它們本身獨特的用處和用法,因此對於生命週期和生命週期函數的善加利用,可讓實際開發事半功倍,並收到良好的效果。 

相關文章
相關標籤/搜索