壹 ❀ 引

我在 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生命周期的更多相关文章

  1. Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!

    点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...

  2. AngularJS开发指南11:AngularJS的model,controller,view详解

    model model这个词在AngularJS中,既可以表示一个(比如,一个叫做phones的model,它的值是一个包含多个phone的数组)对象,也可以表示应用中的整个数据模型,这取决于我们所讨 ...

  3. 详解 Android Activity 生命周期

    从以下几个方面详细说一下Activity的生命周期: 1.回到主屏幕再打开和退出程序的时候. 2.屏幕旋转的时候. 3.打开其它的Activity的情况. 4.打开一个Layou透明的Activity ...

  4. 【Android基础】Fragment 详解之Fragment生命周期

    上一篇文章简单介绍了一下Fragment,这一篇文章会详细的说一下Fragment的生命周期和创建一个用户界面. Fragment的主要功能就是创建一个View,并且有一个生命周期来管理这个View的 ...

  5. 详解Vue2.0生命周期

    网上已经有很多关于vue生命周期的文章,我的这篇文章的由来,其实是我对官网上描述的一句话的思考与理解:“el被新创建的vm.$el替换”,所以文章更多的内容可能是在对vue生命周期中“created ...

  6. 详解React的生命周期

    React生命周期 之前自己在学习React的时候,只是简单的理解了生命周期有这么一些,但是不知道大概的一个流程是怎么样的.那天在面试的时候,问到了.自己也有点懵,也没提前看,不过还是答上来了一些,这 ...

  7. 详解Android Activity生命周期

    转载注明来自:  http://www.cnblogs.com/wujiancheng/ 一.正常情况下Activity的生命周期:  Activity的生命周期大概可以归为三部分 整个的生命周期:o ...

  8. 【Spring Framework】Spring IOC详解及Bean生命周期详细过程

    Spring IOC 首先,在此之前,我们就必须先知道什么是ioc,ioc叫做控制反转,也可以称为依赖注入(DI),实际上依赖注入是ioc的另一种说法, 1.谁控制谁?: 在以前,对象的创建和销毁都是 ...

  9. jQuery height()、innerHeight()、outerHeight()函数的区别详解

    参考来源:http://www.jb51.net/article/84897.htm 代码示例(可复制到编辑器直接打开): <!DOCTYPE html> <html lang=&q ...

随机推荐

  1. Nginx(三)--Nginx 的高可用

    1. Nginx 进程模型简介 多进程+多路复用 master 进程 . worker 进程  启动nginx,查看nginx的线程,可以发现: [root@localhost ~]# ps -ef ...

  2. harbor部署常见的错误

    总结部署harbor过程所遇到的一些坑   1:在使用docker push镜像的时候提示:denied: requested access to the resource is denied,用户和 ...

  3. 70道阿里百度高频Java面试题(框架+JVM+多线程+算法+数据库)

    基础与框架 1.String类能被继承吗,为什么 2.String,Stringbuffer,StringBuilder的区别? 3.ArrayList和LinkedList有什么区别 4.类的实例化 ...

  4. 微信团队分享:极致优化,iOS版微信编译速度3倍提升的实践总结

    1.引言 岁月真是个养猪场,这几年,人胖了,微信代码也翻了. 记得 14 年转岗来微信时,用自己笔记本编译微信工程才十来分钟.如今用公司配的 17 年款 27-inch iMac 编译要接近半小时:偶 ...

  5. srvany.exe读取配置文件问题

    使用instsrv.exe与srvany.exe将自己的程序弄成免登录系统就能自动启动了,然而程序运行需要读取相应的配置文件,所以程序是跑起来了,但不能正常使用,找了很久终于找到了答案.在之前的基础上 ...

  6. PHP ftp获取目录内容为空

    使用PHP的ftp函数获取目录内容,ftp_nlist()和ftp_rawlist()返回都为空. 查了一圈资料找不到答案,然后用Python写了一个,一样的操作就可以获取目录内容. 抓包发现,Pyt ...

  7. Power Platform之Power Automate新增RPA功能

    ​ 什么是RPA RPA( Robotic Process Automation 机器人流程自动化软件),是一种新型的人工智能的虚拟流程自动化机器人.RPA的核心是通过自动化.智能化技术来“替代人”进 ...

  8. Web基础了解版04-XML-Tomcat-Http

    XML 什么是XML - Tomcat - Http XML:eXtensible Markup Language (可扩展标记语言). XML 是一种标记语言,很类似 HTML. XML 的设计宗旨 ...

  9. Maven pom.xml 全配置(二)不常用配置

    Maven pom.xml 全配置(二)不常用配置 这里贴出Maven pom.xml文件中使用率较少的配置参数,如果此篇文档中没有找到你想要的参数,移步Maven pom.xml 全配置(一)常用配 ...

  10. 微服务分布式 spring cloud springboot 框架源码 activiti工作流 前后分离

    1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2. ...