angularjs link compile与controller的区别详解,了解angular生命周期
壹 ❀ 引
我在 angularjs 一篇文章看懂自定义指令directive 一文中简单提及了自定义指令中的link链接函数与compile编译函数,并说到两者具有互斥特性,即同时存在link与compile时link不生效。由于上篇博文篇幅问题,实在不好再过多讨论link,compile,那么本文将围绕三个问题展开,一是再识link与compile函数,你将知道两者为何互斥;二是了解link、compile与controller的区别,存在即合理,在合适的场景下应该使用哪个方法;三是了解指令中代码执行顺序,link与controller执行关系,多层指令又会如何执行?那么本文开始。
贰 ❀ directive中的link与compile
我们已经知道编译函数compile与链接函数link互斥,二者只能存在其一,比如下方例子中,link函数并不会执行:
angular.module('myApp', [])
.controller('myCtrl', function ($scope, $q) {}).directive("echo", function () {
return {
restrict: 'EA',
compile: function () {
console.log('开始编译了!');
},
link: function () {
console.log('开始给DOM绑定事件数据了!')//不执行
}
}
})
那这样就产生了一个问题,是不是compile存在就不能操作link函数了?并不是这样,完整的compile函数其实本身就包含了link函数,有如下两种写法:
写法一:
angular.module('myApp', [])
.controller('myCtrl', function () {})
.directive('echo', function () {
return {
restrict: 'EACM',
scope: {},
replace: true,
controller: function ($scope, $element) {},
compile: function (tEle, tAttrs, transcludeFn) {
//这里模板编译完成但还没被成功返回,我们可以对编译后的DOM树加工
console.log('编译完成,加工DOM吧')
//返回一个函数作为link函数,模板编译已完成,进入链接阶段
return function postLink(scope, ele, attrs) {
console.log('开始执行链接函数link');
};
}
}
})
写法二:
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'EACM',
scope: {},
controller: function ($scope, $element) {},
compile: function () {
//这里模板编译完成但还没被成功返回,我们可以对编译后的DOM树加工
console.log('模板编译完成,可以访问使用指令的dom元素以及元素上的属性了') //返回一个对象作为link函数,只是这个link又分为了两个部分
return {
pre: function (scope, iElement, iAttrs, controller) {
//在子元素被链接之前执行(也就是子元素的postLink执行之前),这里执行DOM转换和链接函数不安全
console.log('pre开始执行了');
},
post: function (scope, iElement, iAttrs, controller) {
// 在子元素被链接之后执行,在这里执行DOM转换和链接函数一样安全
console.log('link开始执行了');
}
}
}
}
})
在上面这个例子中,compile返回的整个对象作为link函数,只是link函数又分为了pre与post两个阶段,我暂且称为preLink与postLink函数,其中postLink对应的就是我们熟悉的Link函数。
postLink我们知道是在DOM元素链接阶段完成之后执行,而preLink有点特殊,它是在所有指令模板编译完成且子指令postLink执行之前执行(这里如果看不懂后面具体会解释),虽然preLink函数中也能给指令模板绑定数据方法,但一般不推荐使用preLink函数,要么使用postLink,或者不写compile直接使用Link函数。
我知道你这里一定有疑问了,preLink和postLink都能给指令绑定事件监听DOM,官方为啥不推荐使用preLink,没用设计它干嘛,二者真就一点区别也没有?当然有,这个我们得先介绍angular的生命周期,不了解这个还真不好解释。
肆 ❀ angular生命周期
通过上文的compile与link了解,我们大致知道了angular生命周期中存在编译阶段与链接阶段两个重要阶段。angular的指令在angular启动前,会以普通文本形式保存在HTML中,但当angular正式启动,这些指令就会经历编译与链接。
1.编译阶段
在编译阶段angular会找到指令,若指令存在模板则开始编译解析模板,但有个问题,指令模板中也可能存在模板,于是还得编译指令模板中子指令的模板,类似深度遍历。
一旦指令DOM编译完成,模板就会返回一个模板函数,我们有机会在指令的模板函数被返回前对编译后的DOM树进行修改,这个机会就在我们前面说的compile函数里。
直到指令和子指令模板DOM编译完成,最外层的父指令模板会统一返回一个模板函数,待模板函数返回完成,编译阶段正式结束。
由于compile处于DOM解析完成且模板函数还未成功返回的阶段,所以compile函数执行一定与编译顺序保持一致,满足从上到下,从外到内的先后顺序执行,我们来看例子:
<body ng-controller="myCtrl">
<div echo1></div>
<div echo2></div>
<div echo3></div>
</body>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo1', function () {
return {
restrict: 'EACM',
template:'<span><echo2></echo2></span>',
compile: function () {
console.log('compile1开始执行');
}
}
})
.directive('echo2', function () {
return {
restrict: 'EACM',
compile: function () {
console.log('compile2开始执行');
}
}
})
.directive('echo3', function () {
return {
restrict: 'EACM',
compile: function () {
console.log('compile3开始执行');
}
}
})
上述例子中,指令echo1拥有子指令echo2与兄弟DOM指令echo3,直到echo2编译完成,echo3才能编译,那么我们知道compile执行与编译阶段保持一致,满足从上到下,从父到子深度遍历的顺序。
由于compile可以对编译出来的DOM进行再加工,所以最终编译出来的DOM树可能与你模板中的DOM结构不一致,因此不推荐在compile阶段做监听DOM事件的操作。
2.链接阶段
在compile执行结束,模板函数被返回并传递给了指令中定义的link函数,此时开始链接阶段;链接阶段负责将编译阶段编译好的DOM树与scope相关联,这样link函数就能将定义好的数据,事件与DOM绑定在一起,实现DOM操作与监听。
前面也说了指令也会有子指令,而这个preLink则在编译完成(compile)之后子指令链接之前(preLink)执行,所以preLink也在compile之后,且在子指令的preLink与postLink之前执行。
postLink比较特殊,postLink永远在编译完成且子指令链接之后执行(postLink之后),所以也是在compile之后,且在子指令的postLink之后。
有点混乱了,理一理,以单个指令来说,它应该是编译阶段开始---DOM编译成功执行compile---返回模板函数(编译结束)---模板函数传递给link---链接阶段开始,DOM与scope关联---执行pre---执行post---链接阶段结束。
而当指令包含子指令时,它应该是编译阶段开始---父指令DOM编译成功执行父compile---返回模板函数---子指令DOM编译成功执行子compile---返回模板函数---模板函数传递给link---链接阶段开始,DOM与scope关联---执行父pre---执行子pre---执行子post---执行父post---链接阶段结束。
看个例子:
<body ng-controller="myCtrl">
<div echo></div>
</body>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'EACM',
template:'<span><echo1></echo1></span>',
compile: function () {
console.log('compile1开始执行');
return {
pre: function () {
console.log('pre1开始执行');
},
post: function () {
console.log('post1开始执行');
}
}
}
}
})
.directive('echo1', function () {
return {
restrict: 'EACM',
compile: function () {
console.log('compile2开始执行');
return {
pre: function () {
console.log('pre2开始执行');
},
post: function () {
console.log('post2开始执行');
}
}
}
}
})
compile与pre就像深度遍历,有子就一直往下执行,post就像回溯,从里往外执行。
3.preLink与postLink的区别
前面我们留下了一个问题,preLink到底有什么用,我们来看下面这段代码,猜猜会如何执行:
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'EACM',
template: '<span><echo1></echo1></span>',
link: function (scope) {
scope.name = '听风是风';
}
}
})
.directive('echo1', function () {
return {
restrict: 'EACM',
template: '<span>{{describe}}</span>',
link: function (scope) {
scope.describe = '我的名字是' + scope.name;
}
}
})
导致scope.name无法取到值的原因是,这里的link函数就是我们之前提到的postLink函数,postLink函数执行就像回溯,子指令先执行,所以取值的时候父指令还未声明此变量。想要做到父指令给子指令作用域传值,preLink就能做到这一点:
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'EACM',
template: '<span><echo1></echo1></span>',
compile: function () {
return {
pre: function (scope) {
scope.name = '听风是风';
},
post: function () { }
}
}
}
})
.directive('echo1', function () {
return {
restrict: 'EACM',
template: '<span>{{describe}}</span>',
link: function (scope) {
scope.describe = '我的名字是' + scope.name;
}
}
})
那么到这里我们知道了pre与post的区别,pre可以利用自己执行顺序的优势给子指令作用域直接传值,但是仍然不推荐这么做,这里我们只是作为知识了解。毕竟指令应该拥有干净隔离的作用域,也不会用到这种传值模式。
叁 ❀ link、compile、controller的职责
那么通过上文的介绍,我们知道了link与compile对应了链接和编译两个阶段,编译函数负责对模板DOM进行转换,在作用域同DOM链接之前可以手动操作DOM。在开发中编写自定义指令时这种操作是非常罕见,所以compile使用不多。
链接函数负责将作用域和DOM进行链接,编译函数会在模板编译完成并同作用域进行链接后被调用,因此它负责设置事件监听器,监视数据变化和实时的更新DOM,这与controller十分类似。
抛开加工DOM的compile不说,那我们应该在什么情况下使用controller和link呢,毕竟这两兄弟都能做DOM事件监听与数据更新;其实很简单,如果你希望这个指令的属性方法能被其它指令复用,那就将方法属性定义在controller中,如果只是希望给指令自己使用,那就加在link函数中。
之所以这么说,是因为指令有一个require属性,通过require,我们能将require值同名指令的controller加入到当前指令中,然后就可以通过link函数的第四个参数直接使用被require指令controller中的属性方法了,来看个例子:
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'EACM',
template: '<span><echo1></echo1></span>',
controller: function () {
this.sayName = function (name) {
console.log('我的名字是' + name);
}
}
}
})
.directive('echo1', function () {
return {
restrict: 'EACM',
template: '<button ng-click="myName(name)">点我</button>',
require: '^echo',
link: function (scope, ele, attr, ctrl) {
console.log(ctrl);
scope.name = '听风是风';
scope.myName = ctrl.sayName;
}
}
})
可以看到在指令echo1中成功注入了指令echo的controller,我们能在echo1中直接使用echo的方法,这就是为何说如果指令方法需要复用,建议绑在controller中的原因。
肆 ❀ 总
那么到这里,我们知道了compile与link是互斥关系,如果同时写了两个函数,link不会执行,这是因为compile本身就会返回一个函数作为link,哪怕你不写返回函数,那也认定你返回了一个空的link。
其次,我们知道了pre与post的区别,这两个函数虽然同属于link的一部分,但在生命周期中扮演了不同的角色,pre在子元素链接完成前执行,而post在子元素链接完成之后,这样导致了父的post反而比子post晚一步执行
我们简单介绍了angular生命周期中两个重要的过程,编译阶段与链接阶段,通过这两个阶段,也解释了为什么compile与pre执行像深度遍历,而post像回溯的原因。
最后,我们将controller,link,compile工作职责做了一个简单介绍,link与controller很像,但如果你想指令属性方法复用,推荐绑定在controller上,如果只是指令自己使用,推荐加在link上。
最后我还要留一个疑问,为什么在最后的例子中,指令echo1想复用指令echo controller上的方法,方法sayName是绑定在this上的,如果绑在scope上能不能复用呢?angular中的scope和this到底是什么关系?这个我会在下篇博客中好好介绍。
博客已更新 angularjs $scope与this的区别,controller as vm有何含义?
那么本文到这里,结束。
angularjs link compile与controller的区别详解,了解angular生命周期的更多相关文章
- Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!
点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...
- AngularJS开发指南11:AngularJS的model,controller,view详解
model model这个词在AngularJS中,既可以表示一个(比如,一个叫做phones的model,它的值是一个包含多个phone的数组)对象,也可以表示应用中的整个数据模型,这取决于我们所讨 ...
- 详解 Android Activity 生命周期
从以下几个方面详细说一下Activity的生命周期: 1.回到主屏幕再打开和退出程序的时候. 2.屏幕旋转的时候. 3.打开其它的Activity的情况. 4.打开一个Layou透明的Activity ...
- 【Android基础】Fragment 详解之Fragment生命周期
上一篇文章简单介绍了一下Fragment,这一篇文章会详细的说一下Fragment的生命周期和创建一个用户界面. Fragment的主要功能就是创建一个View,并且有一个生命周期来管理这个View的 ...
- 详解Vue2.0生命周期
网上已经有很多关于vue生命周期的文章,我的这篇文章的由来,其实是我对官网上描述的一句话的思考与理解:“el被新创建的vm.$el替换”,所以文章更多的内容可能是在对vue生命周期中“created ...
- 详解React的生命周期
React生命周期 之前自己在学习React的时候,只是简单的理解了生命周期有这么一些,但是不知道大概的一个流程是怎么样的.那天在面试的时候,问到了.自己也有点懵,也没提前看,不过还是答上来了一些,这 ...
- 详解Android Activity生命周期
转载注明来自: http://www.cnblogs.com/wujiancheng/ 一.正常情况下Activity的生命周期: Activity的生命周期大概可以归为三部分 整个的生命周期:o ...
- 【Spring Framework】Spring IOC详解及Bean生命周期详细过程
Spring IOC 首先,在此之前,我们就必须先知道什么是ioc,ioc叫做控制反转,也可以称为依赖注入(DI),实际上依赖注入是ioc的另一种说法, 1.谁控制谁?: 在以前,对象的创建和销毁都是 ...
- jQuery height()、innerHeight()、outerHeight()函数的区别详解
参考来源:http://www.jb51.net/article/84897.htm 代码示例(可复制到编辑器直接打开): <!DOCTYPE html> <html lang=&q ...
随机推荐
- kafka生产消息,streaming消费
package com.bd.useranalysis.spark.streaming.kafka2es; import com.alibaba.fastjson.JSON; import com.b ...
- 使用docker安装虚拟机并打开ssh连接
一.需求 最近有个需求,要连接很多个linux系统进行测试软件功能,但是我这里只有几个虚拟机,所以需要使用docker来安装几十个或者上百个虚拟机来进行测试. 二.安装docker 这里就不演示怎么安 ...
- 关于页面打印window.print()的样式问题
当我们打印网页的时候.有时候会发现.打印出来的.跟网页上看到的样式的差别有点大.这其中可能有的问题是.样式问题. 当调用打印(window.print())方法时.打印机会在网页的样式中查找 @med ...
- python基础入门 整型 bool 字符串
整型,bool值,字符串 一.整型 整型十进制和二进制 整型:整型在Python中的关键字用int来表示; 整型在计算机中是用于计算和比较的 可进行+ - * / % //(整除) **(幂运算) 十 ...
- Ansible-上部
Ansible概述 Ansible是一个配置管理系统configuration management systempython 语言是运维人员必须会的语言ansible 是一个基于python 开发的 ...
- 2016/09/29 SQL中的join
1.建表 首先建tb_a并插入数据. )); insertinto tb_a(symbol, sname) values ('A','B'); insertinto tb_a(symbol, snam ...
- 《Java知识应用》Java加密方式(Base64)详解
1. 说明 Base64加密方式:比较简单,加密快,对普通大众可以起到加密的作用.在程序员眼中和透明一样. Base64应用场景:图片转码(应用于邮件,img标签,http加密) 2. 案例 impo ...
- js对象属性的查询(点运算符和方括号运算符的区别)
js中可以通过点(.)和方括号([ ])运算符来获取属性的值.运算符的左侧应该是一个表达式,它返回一个对象.对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符.对于方括号 ([ ])来说方括号 ...
- python基础知识第六篇(知识点总结)
####################### 整理 ################# # 一.数字# int(..) # 二.字符串# replace(替换)/find/join/strip(移除 ...
- Java语法进阶10-泛型
泛型 泛型:参数化的类型,即把数据类型当做参数来传递 有的地方又称为泛化的类型,用一个单个大写字母,例如<T>来代表任意类型,这个T就是泛化的类型. 泛型的好处: (1)表示某个变量的类型 ...