瞭解angularjs中的生命週期鉤子函數$onInit,$onChange,$onDestory,$postLink

 壹 ❀ 引html

我在前面花了三篇文章用於介紹angularjs的指令directive,組件component,並專門花了一篇文章介紹directive與component的不一樣,其中提到在component的聲明週期中須要配合鉤子函數來實現組件部分功能,例如在bindings傳值過程當中,你得經過$onInit方法來初始化數據,那麼咱們就來好好聊聊component中經常使用的幾個鉤子函數,本文開始。angularjs

 貳 ❀ $onInitsegmentfault

在介紹component的文章中已經有涉及$onInit方法的說明,$onInit用於在component的controller中作數據初始化的操做。app

常理上來講,即使咱們不經過$onInit爲組件綁定數據也是沒問題的,看個簡單的例子:函數

<div ng-controller="myCtrl">
    <echo></echo>
</div>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .component('echo', {
        template: '<div>{{vm.name}}</div><button ng-click="vm.sayName()">點我</button>',
        controllerAs: 'vm',
        controller: function () {
            this.name = '聽風是風';
            this.sayName = function () {
                console.log(this.name);
            };
        }
    });

可若是咱們須要使用bindings傳遞父做用域的數據,或者利用require注入上層組件的controller時,就必定得使用$onInit方法才能拿到傳遞過來的數據,來看個例子:post

<div ng-controller="myCtrl">
    <jack>
        <echo my-name="{{name}}"></echo>
    </jack>
</div>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {
        $scope.name = '時間跳躍';
    })
    .component('jack', {
        controller: function () {
            this.name = '聽風是風';
        }
    })
    .component('echo', {
        controllerAs: 'vm',
        require: {
            jack: '^^'
        },
        bindings: {
            myName: '@'
        },
        controller: function () {
            console.log(this.myName); //undefined
            console.log(this.jack); //undefined
            this.$onInit = function () {
                console.log(this.myName); //時間跳躍
                console.log(this.jack); // controller {name: "聽風是風"}
            }
        }
    });

在上面的例子中我分別在父做用域上綁定了一個name屬性,並經過bindings傳遞給組件echo,並注入了父組件jack的控制器,能夠看到只有在$onInit中才能正確的拿到它們,這就是$onInit的做用。ui

 叄 ❀ $onChangesthis

事實上$onInit的初始化只會執行一遍,若是咱們經過bindings傳入了父做用域中的數據,父做用域的數據改變其實子組件是沒法感知的,咱們看個例子:spa

<div ng-controller="myCtrl">
    <div>個人名字是{{myself.name}},我今年{{myself.age}}了</div>
    <echo my-age="myself.age" my-name="myself.name"></echo>
    <button ng-click="reduce()">一鍵返老還童</button>
</div>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {
        $scope.myself = {
            name: '聽風是風',
            age: 26
        };
        $scope.reduce = function () {
            $scope.myself.age--;
        };
    })
    .component('echo', {
        controllerAs: 'vm',
        bindings: {
            myName: "<",
            myAge: '<'
        },
        template: '<div>個人名字是{{vm.name}},我今年{{vm.age}}了</div>',
        controller: function () {
            this.$onInit = function () {
                this.name = this.myName;
                this.age = this.myAge;
            };
        }
    });

我將父做用域對象中一條屬性經過bindings傳遞給子組件後,經過點擊事件不斷減少age屬性,能夠看到組件中沒辦法感知到變化。那麼這種狀況就能夠利用$onChanges實現,咱們修改代碼:翻譯

angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {
        $scope.myself = {
            name: '聽風是風',
            age: 26
        };
        $scope.reduce = function () {
            $scope.myself.age--;
        };
    })
    .component('echo', {
        controllerAs: 'vm',
        bindings: {
            myName: "<",
            myAge: '<'
        },
        template: '<div>個人名字是{{vm.name}},我今年{{vm.age}}了</div>',
        controller: function () {
            this.$onInit = function () {
                this.name = this.myName;
            };
            this.$onChanges = function (changes) {
                console.log(changes);
                this.age = changes.myAge.currentValue;
            };
        }
    });

有人確定會問了,這個$onChanges這麼好用,那能不能直接取代$onInit作初始化呢,這個changes參數又是啥,咱們能夠打印它:

 

changes表明的正是bindings中變化的變量,這裏一共輸出了2次,第一次是組件初始化時myName與myAge從無到有,第二次輸出是由於點擊致使age減小,因而可知$onChanges只能監聽到bindings中變化的變量,並不適合作初始化。

 肆 ❀ $postLink

咱們知道directive由於編譯函數與連接函數的存在,咱們能夠在DOM編譯階段操做DOM以及連接階段綁定數據,而component提供的$postLink方法可在組件自身模板和其子級組件模板已連接以後被調用。

經過angularjs生命週期咱們知道,組件老是先編譯完成,再將模板與scope連接,且連接過程是從子往父回溯綁定,由這一點能夠肯定$postLink執行時子組件的模板必定已經編譯完成,咱們來看個例子:

<div ng-controller="myCtrl">
    <jack>
        <echo></echo>
    </jack>
</div>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .component('jack', {
        transclude: true,
        template: '<div>parent</div><div ng-transclude></div>',
        controller: function ($scope) {
            var child = document.querySelector('#child');
            console.dir(child);//?
            this.$postLink = function () {
                var child = document.querySelector('#child');
                console.dir(child);//?
            };

        }
    })
    .component('echo', {
        template: '<div id="child">child</div>',
    });

在上述代碼中,我想在父組件jack的控制器中獲取子組件id爲child的元素,因而我分別在控制器外層與$postLink中分別獲取兩次,並打印它們,能夠看到$postLink中成功獲取到了子模板元素。

咱們根據斷點查看DOM變化,在打印第一個時,子組件模板還沒加載,而跑到$postLink時,子組件模板已經加載完畢了,因此此時咱們能夠獲取到正確的DOM。

 伍 ❀ $onDestroy

$onDestroy用於在做用域被銷燬時,用於清除掉那些咱們先前自定義的事件監聽或者定時器等。

咱們知道angularjs在$scope上提供了一個$destory方法用於主動銷燬當前做用域,對於這個方法陌生能夠看看angularjs權威指南的解釋:

 咱們知道就算是JS用於垃圾回收機制在操做DOM或者定時器時也得在必要的時候手動釋放它們,angularjs雖然也存在自動清理做用域的狀況,但它也沒辦法銷燬咱們定義的原生JS邏輯,來看個例子:

<div ng-controller="myCtrl">
    <button ng-click="sayName()">sayName</button>
    <button ng-click="ruin()">destory</button>
</div>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope, $interval) {
        $scope.ruin = function () {
            console.log('銷燬做用域');
            //銷燬做用域
            $scope.$destroy();
        };
        $scope.sayName = function () {
            console.log('聽風是風');
        };

        //原生定時器
        var s1 = setInterval(function () {
            console.log('我是原生定時器');
        }, 3000);
        //angular提供的定時器
        var s2 = $interval(function () {
            console.log('我是angular定時器');
        }, 3000);
    })

在這個例子中,咱們分別用建立了一個原生定時器與angular的封裝定時器,在銷燬做用域後能夠看到綁定的sayName失效,但兩個定時器仍然在起做用。怎麼感知銷燬並清除掉2個定時器呢,angular是這麼作的,添加以下代碼:

$scope.$on('$destroy', function () {
    clearInterval(s1);
    $interval.cancel(s2);
})

能夠看到咱們經過$on監聽銷燬後清除了定時器起到了做用,以後定時器並未執行。

那麼說道這你確定就疑問了,$on都能監聽銷燬,我在組件間也能這麼用,那還要什麼$onDestroy鉤子函數,其實我一開始也有這個疑問,我在谷歌百度了相關資料,惟一得的合理就是,脫離$scope的約束。

咱們知道在angularjs早期版本數據都是推崇綁定scope上,在後面版本咱們能夠經過as vm的作法將數據綁定在控制器this上,而組件的出現,你會發現組件傳值都是默認綁定在控制器this上,也就是說在組件component開發中咱們不用注入$scope都能作到任意數據綁定,而鉤子函數$onDestroy的出現真是知足了這一點。

實際開發中咱們不多使用$scope.$destroy()手動銷燬做用域,咱們要作的僅僅是感知銷燬並作要作的事情而已,來看個例子:

<body ng-controller="myCtrl">
    <jack>
        <echo></echo>
    </jack>
</body>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope, $interval) {})
    .component('jack', {
        transclude: true,
        template: '<div>我是parent</div><button ng-click="$ctrl.destroy()">destory</button><div ng-transclude></div>',
        controller: function ($scope) {
            var s1 = setInterval(function () {
                console.log(1);
            }, 3000);
            //銷燬做用域
            this.destroy = function () {
                $scope.$destroy();
            };
            //用了scope的銷燬監聽
            $scope.$on('$destroy', function () {
                clearInterval(s1);
            });
        }
    })
    .component('echo', {
        template: '<div>我是child</div>',
        controller: function () {
            this.s2 = setInterval(function () {
                console.log(1);
            }, 3000);
            //用了鉤子函數的監聽
            this.$onDestroy = function () {
                clearInterval(this.s2);
            };
        }
    });

這裏我建立了父組件jack與子組件echo,並分別用scope與鉤子函數的做用域銷燬監聽方法,能夠看到一旦父組件做用域銷燬,父子組件中的監聽函數都起到了做用。

但按照component擁有隔離做用域的特色,銷燬父組件做用域應該不會影響子組件纔對,因此這裏的效果反而解釋不通;我在上一個例子中銷燬了外層控制器的做用域確實沒對組件形成影響。

 陸 ❀ 總

其實文章說了這麼多,到頭來發現只有$onInit與$onChange在實際開發中會頗有用,另外兩個方法很是冷門,以致於在查閱資料時很是吃力,但站在瞭解的角度也是不錯的,萬一之後有需求須要使用呢?

若是對於angularjs指令,組件以及它們區別有興趣,能夠閱讀博主相關文章:

angularjs 一篇文章看懂自定義指令directive

一篇文章看懂angularjs component組件

angularjs中directive指令與component組件有什麼區別?

angularjs $scope與this的區別,controller as vm有何含義?

那麼本文到這裏結束。

 參考

翻譯:深刻理解Angular 1.5 中的生命週期鉤子

AngularJS: $onDestroy component hook makes $scope unnecessary in hybrid Cordova mobile application events unbinding and in interval/timeout cleaning

$postLink of an angular component/directive running too early

相關文章
相關標籤/搜索