了解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

相关文章
相关标签/搜索