angularjs compine和link的区别
[译]ng指令中的compile与link函数解析
通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre-link,post-link的用法与区别.
angularjs里的指令非常神奇,允许你创建非常语义化以及高度重用的组件,可以理解为web components的先驱者.
网上已经有很多介绍怎么使用指令的文章以及相关书籍,相互比较的话,很少有介绍compile与link的区别,更别说pre-link与post-link了.
大部分教程只是简单的说下compile会在ng内部用到,而且建议大家只用link属性,大部分指令的例子里都是这样的
这是非常不幸的,因为正确的理解这些函数的区别会提高你对ng内部运行机理的理解,有助于你开发更好的自定义指令.
所以跟着我一起来看下面的内容一步步的去了解这些函数是什么以及它们应该在什么时候用到
- 本文假设你已经对指令有一定的了解了,如果没有的话强烈建议你看看这篇文章AngularJS developer guide section on directives
NG中是怎么样处理指令的
开始分析之前,先让我们看看ng中是怎么样处理指令的.
当浏览器渲染一个页面时,本质上是读html标识,然后建立dom节点,当dom树创建完毕之后广播一个事件给我们.
当你在页面中使用script标签加载ng应用程序代码时,ng监听上面的dom完成事件,查找带有ng-app属性的元素.
当找到这样的元素之后,ng开始处理dom以这个元素的起点,所以假如ng-app被添加到html元素上,则ng就会从html元素开始处理dom.
从这个起点开始,ng开始递归查找所有子元素里面,符合应用程序里定义好的指令规则.
ng怎样处理指令其实是依赖于它定义时的对象属性的,你可以定义一个compile或者一个link函数,或者用pre-link和post-link函数来代替link.
所以这些函数的区别呢?为什么要使用它?以及什么时候使用它呢?
带着这些问题跟着我一步一步来解答这些迷团吧
一段代码
为了解释这些函数的区别,下面我将使用一个简单易懂的例子
- 如果您有任何的问题,请不要犹豫赶紧在下面加上你的评论吧.
看看下面一段html标签代码
<level-one>
<level-two>
<level-three>
Hello
</level-three>
</level-two>
</level-one>
然后是一段js代码
var app = angular.module('plunker', []);
function createDirective(name){
return function(){
return {
restrict: 'E',
compile: function(tElem, tAttrs){
console.log(name + ': compile');
return {
pre: function(scope, iElem, iAttrs){
console.log(name + ': pre link');
},
post: function(scope, iElem, iAttrs){
console.log(name + ': post link');
}
}
}
}
}
}
app.directive('levelOne', createDirective('levelOne'));
app.directive('levelTwo', createDirective('levelTwo'));
app.directive('levelThree', createDirective('levelThree'));
结果非常简单:让ng来处理三个嵌套指令,并且每个指令都有自己的complile,pre-link,post-link函数,每个函数都会在控制台里打印一行东西来标识自己.
这个例子能够让我们简单的了解到ng在处理指令时,内部的流程
代码输出
下面是一个在控制台输出结果的截图

如果想自己试一下这个例子的话,请点击this plnkr,然后在控制台查看结果.
分析代码
第一个要注意的是这些函数的调用顺序:
// COMPILE PHASE
// levelOne: compile function is called
// levelTwo: compile function is called
// levelThree: compile function is called
// PRE-LINK PHASE
// levelOne: pre link function is called
// levelTwo: pre link function is called
// levelThree: pre link function is called
// POST-LINK PHASE (Notice the reverse order)
// levelThree: post link function is called
// levelTwo: post link function is called
// levelOne: post link function is called
这个例子清晰的显示出了ng在link之前编译所有的指令,然后link要又分为了pre-link与post-link阶段.
注意下,compile与pre-link的执行顺序是依次执行的,但是post-link正好相反.
所以上面已经明确标识出了不同的阶段,但是compile与pre-link有什么区别呢,都是相同的执行顺序,为什么还要分成两个不同的函数呢?
DOM
为了挖的更深一点,让我们简单的修改一下上面的代码,它也会在各个函数里打印参数列表中的element变量
var app = angular.module('plunker', []);
function createDirective(name){
return function(){
return {
restrict: 'E',
compile: function(tElem, tAttrs){
console.log(name + ': compile => ' + tElem.html());
return {
pre: function(scope, iElem, iAttrs){
console.log(name + ': pre link => ' + iElem.html());
},
post: function(scope, iElem, iAttrs){
console.log(name + ': post link => ' + iElem.html());
}
}
}
}
}
}
app.directive('levelOne', createDirective('levelOne'));
app.directive('levelTwo', createDirective('levelTwo'));
app.directive('levelThree', createDirective('levelThree'));
注意下console.log里的输出,除了输出原始的html标记基本没别的改变.
这个应该能够加深我们对于这些函数上下文的理解.
再次运行代码看看
输出
下面是一个在控制台输出结果的截图

假如你还想自己运行看看效果,可以点击this plnkr,然后在控制台里查看输出结果.
观察
输出dom的结果可以暴露一些有趣的事情:dom内容在compile与pre-link两个函数中是不一样的
所以发生了什么呢?
Compile
我们已经知道当ng发现dom构建完成时就开始处理dom.
所以当ng在遍历dom的时候,碰到level-one元素,从它的定义那里了解到,要执行一些必要的函数
因为compile函数定义在level-one指令的指令对象里,所以它会被调用并传递一个element对象作为它的参数
如果你仔细观察,就会看到,浏览器创建这个element对象时,仍然是最原始的html标记
- 在
ng中,原始dom通常用来标识template element,所以我在定义compile函数参数时就用到了tElem名字,这个变量指向的就是template element.
一旦运行levelone指令中的compile函数,ng就会递归深度遍历它的dom节点,然后在level-two与level-three上面重复这些操作.
Post-link
深入了解pre-link函数之前,让我们来看看post-link函数.
- 如果你在定义指令的时候只使用了一个
link函数,那么ng会把这个函数当成post-link来处理,因此我们要先讨论这个函数
当ng遍历完所有的dom并运行完所有的compile函数之后,就反向调用相关联的post-link函数.
dom现在开始反向,并执行post-link函数,因此,在之前这种反向的调用看起来有点奇怪,其实这样做是非常有意义的.

当运行包含子指令的指令post-link时,反向的post-link规则可以保证它的子指令的post-link是已经运行过的.
所以,当运行level-one指令的post-link函数的时候,我们能够保证level-two和level-three的post-link其实都已经运行过了.
这就是为什么人们都认为post-link是最安全或者默认的写业务逻辑的地方.
但是为什么这里的element跟compile里的又不同呢?
一旦ng调用过指令的compile函数,就会创建一个template element的element实例对象,并且为它提供一个scope对象,这个scope有可能是新实例,也有可能是已经存在,可能是个子scope,也有可能是独立的scope,这些都得依赖指令定义对象里的scope属性值
所以当linking发生时,这个实例element以及scope对象已经是可用的了,并且被ng作为参数传递到post-link函数的参数列表中去.
- 我个人总是使用
iElem名称来定义一个link函数的参数,并且它是指向element实例的
所以post-link(pre-link)函数的element参数对象是一个element实例而不是一个template element.
所以上面例子里的输出是不同的
Pre-link
当写了一个post-link函数,你可以保证在执行post-link函数的时候,它的所有子级指令的post-link函数是已经执行过的.
在大部分的情况下,它都可以做的更好,因此通常我们都会使用它来编写指令代码.
然而,ng为我们提供了一个附加的hook机制,那就是pre-link函数,它能够保证在执行所有子指令的post-link函数之前.运行一些别的代码.
这句话是值得反复推敲的
pre-link函数能够保证在element实例上以及它的所有子指令的post-link运行之前执行.
所以它使的post-link函数反向执行是相当有意义的,它自己是原始的顺序执行pre-link函数
这也意为着pre-link函数运行在它所有子指令的pre-link函数之前,所以完整的理由就是:
一个元素的pre-link函数能够保证是运行在它所有的子指令的post-link与pre-link运行之前执行的.见下图

回顾
如果我们回头看看上面原始的输出,就能清楚的认出到底发生了什么:
// HERE THE ELEMENTS ARE STILL THE ORIGINAL TEMPLATE ELEMENTS
// COMPILE PHASE
// levelOne: compile function is called on original DOM
// levelTwo: compile function is called on original DOM
// levelThree: compile function is called on original DOM
// AS OF HERE, THE ELEMENTS HAVE BEEN INSTANTIATED AND
// ARE BOUND TO A SCOPE
// (E.G. NG-REPEAT WOULD HAVE MULTIPLE INSTANCES)
// PRE-LINK PHASE
// levelOne: pre link function is called on element instance
// levelTwo: pre link function is called on element instance
// levelThree: pre link function is called on element instance
// POST-LINK PHASE (Notice the reverse order)
// levelThree: post link function is called on element instance
// levelTwo: post link function is called on element instance
// levelOne: post link function is called on element instance
概要
回顾上面的分析我们可以描述一下这些函数的区别以及使用情况:
Compile 函数
使用compile函数可以改变原始的dom(template element),在ng创建原始dom实例以及创建scope实例之前.
可以应用于当需要生成多个element实例,只有一个template element的情况,ng-repeat就是一个最好的例子,它就在是compile函数阶段改变原始的dom生成多个原始dom节点,然后每个又生成element实例.因为compile只会运行一次,所以当你需要生成多个element实例的时候是可以提高性能的.
template element以及相关的属性是做为参数传递给compile函数的,不过这时候scope是不能用的:
下面是函数样子
/**
* Compile function
*
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(tElem, tAttrs){
// ...
};
Pre-link 函数
使用pre-link函数可以运行一些业务代码在ng执行完compile函数之后,但是在它所有子指令的post-link函数将要执行之前.
scope对象以及element实例将会做为参数传递给pre-link函数:
下面是函数样子
/**
* Pre-link function
*
* @param scope - scope associated with this istance
* @param iElem - instance element
* @param iAttrs - attributes of the instance element
*/
function(scope, iElem, iAttrs){
// ...
};
Post-link 函数
使用post-link函数来执行业务逻辑,在这个阶段,它已经知道它所有的子指令已经编译完成并且pre-link以及post-link函数已经执行完成.
这就是被认为是最安全以及默认的编写业务逻辑代码的原因.
scope实例以及element实例做为参数传递给post-link函数:
下面是函数样子
/**
* Post-link function
*
* @param scope - scope associated with this istance
* @param iElem - instance element
* @param iAttrs - attributes of the instance element
*/
function(scope, iElem, iAttrs){
// ...
};
总结
现在你应该对compile,pre-link,post-link这此函数之间的区别有了清晰的认识了吧.
如果还没有的话,并且你是一个认真的ng开发者,那么我强烈建议你重新把这篇文章读一读直到你了解为止
理解这些概念非常重要,能够帮助你理解ng原生指令的工作原理,也能帮你优化你自己的自定义指令.
如果还有问题的话,欢迎大家在下面评论里加上你的问题
以后还会接着分析关于指令里的其它两个问题:
指令使用
transclusion属性是怎么工作的?指令的
controller函数是怎么关联的
最后,如果发现本文哪里有不对的,请及时给我发评论
谢谢!
angularjs compine和link的区别的更多相关文章
- @import和link的区别
@import和link的区别 1.link语法结构 <link href="CSSurl路径" rel="stylesheet" type=&qu ...
- Symbolic link and hard link的区别(linux)
--Symbolic link and hard link的区别(linux) --------------------------------------------------2014/06/10 ...
- AngularJS进阶(二十一)Angularjs中scope与rootscope区别及联系
Angularjs中scope与rootscope区别及联系 scope是html和单个controller之间的桥梁,数据绑定就靠他了.rootscope是各个controller中scope的桥梁 ...
- AngularJS的指令(Directive) compile和link的区别及使用示例
如果我想实现这样一个功能,当一个input失去光标焦点时(blur),执行一些语句,比如当输入用户名后,向后台发ajax请求查询用户名是否已经存在,好有及时的页面相应. 输入 camnpr 失去焦点后 ...
- angularjs $scope与this的区别,controller as vm有何含义?
壹 ❀ 引 初学angularjs的同学对于$scope一定不会陌生,scope(作用域)是将view(视图)与model(模板)关联起来的桥梁,通过controller(控制器)对于model的数 ...
- angularjs compile和link
原文:http://www.cnblogs.com/GoodPingGe/p/4361354.html ************************************************ ...
- css中import与link用法区别
方式:引入CSS的方法有两种,一种是@import,一种是link @import url('地址');//注意,这种方式可以放在页面也可以放在css文件中<link href="地址 ...
- css文件加载:@import和link的区别
刚看了一个百度试题:请简述CSS加载方式link和@import的区别? 平时一般都用link,也知道css的加载方式,但还真的没有仔细研究过其之间的差别,查了一些资料,大致总结如下: @impot使 ...
- link @import区别 src href的区别
先说页面引入css的四种方式吧 1 在头部写在style里面 2 行内样式 tyle= 3 外部引入 link和@import的区别 link属于XHTML的标签,而@import只是css提供的一种 ...
随机推荐
- Kafka Topic的详细信息 捎带主要的安装步骤
1. 安装步骤 Kafka伪分布式安装的思路跟Zookeeper的伪分布式安装思路完全一样,不过比Zookeeper稍微简单些(不需要创建myid文件), 主要是针对每个Kafka服务器配置一个单独的 ...
- Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释
一.线程5种状态 新建状态(New) 新创建了一个线程对象. 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获 ...
- 前端 JS 修炼(第一天)包装对象、作用域、创建对象
1.js基本概念以及注意 直接量 :程序中直接使用的数据值.下面列出的都是直接量: 1 12 //数字 2 1.2 //小数 3 "hello world" //字符串文本 4 t ...
- Python 爬虫从入门到进阶之路(七)
在之前的文章中我们一直用到的库是 urllib.request,该库已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好,而 Requests 自称 “HTTP for Hum ...
- Unity AssetBundle,Asset,GameObject之间的联系
一.问题 首先,这里说明一下,我这边的GameObject有点笼统,就是表达的是游戏中的具体实例. 二.概念 1)Asset是什么? 游戏中具体的资源,像texture,mesh,material,s ...
- 搭建Elk集群搭建 ES-filebeat-logstrash-kibana
一 .基础环境 软件 版本 作用 Linux/Win Server2012 CentOs/Win Server2012 服务器环境 JDK 1.8.0_151 运行环境依赖 Elasticsearch ...
- spring 5.x 系列第17篇 —— 整合websocket (xml配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 项目模拟一个简单的群聊功能,为区分不同的聊 ...
- Java学习笔记——三层架构
Layer: UI层: user interface 用户接口层 Biz层: service business login layer 业务逻辑层 DAO层: Date Access Obje ...
- linux 安装 websocketd
1.下载 wget https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_am ...
- GRPC 截止时间与元数据
截止时间 gRPC 允许客户端在调用一个远程方法前指定一个最后期限值.这个值指定了在客户端可以等待服务端多长时间来应答,超过这个时间值 RPC 将结束并返回DEADLINE_EXCEEDED错误.在服 ...