AngularJS指令compile和link函数的真相
太棒了。它允许你创建高度语义化和可重用的组建。从某种意义上说,您可以将它们视为Web组件的最终先驱。
关于如何写自定义的指令已经出现在很多好的文章和书中了。相反,很骚有涉及到和link函数的不同,更不要说pre-link与post-link函数了。
大多数教程都简洁的提及到函数主要被用在内部并且建议你只使用link函数这可以覆盖大多数自定义指令使用场景。
因此,跟谁我,在本文的最后,您将确切知道这些功能是什么以及何时应该使用它们。
This that you know what an is. If not, I would the guide on first. 是如何处理指令的
在我们开始之前,让我们先来分解如何处理指令。
当浏览器渲染一个页面时,它一定会去阅读HTML标签,创建DOM并且在DOM准备就绪时广播事件。
当你使用在你的代码页中引入时,监听那事件当它一旦收到事件,它开始遍历DOM,查询一个有ng-app属性的标签。
当这个标签被查找到,开始使用该特定元素作为起点处理DOM。因此,如果在html元素上设置ng-app属性,将开始处理从html元素开始的DOM。
从这时起,递归地查询所有的子元素,寻找对应于你的应用程序中定义的指令的模式。
如何处理元素取决于实际的指令定义对象。你定义了一个函数,一个link函数或者两者。或者你可以定义一个pre-link函数和一个post-link函数来代替link函数。
那么,所有这些功能之间有什么区别,以及为什么或什么时候应用它们?
跟紧我...
代码演示
为了解释差异,我将使用一些希望易于理解的示例代码。
考虑下面的HTML标记:
Hello {{name}}
和一下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'));
目的很简单:使处理三个嵌套的指令,每个指令都有自己的,pre-link,post-link函数去log一行文本保证我们能够辨识它们。
这应该使我们能够首先了解处理指令后发生的情况。
输出
以下是控制台中输出的屏幕截图:
自己尝试一下,仅仅打开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
这清晰的证明了是如何第一编译所有指令在把他们link他们是scope之前,并且在link阶段被分解为了pre-link,post-link阶段
注意到与pre-link函数调用顺序是相同的但是post-link与他们刚好相反。
所以在这一点上我们已经可以清楚地识别出不同的阶段,那么与pre-link有什么区别了?他们执行的顺序是相同的,那为什么要把他们分开?
The DOM
为了深入研究,让我们更新,以便在每个函数调用期间输出元素的DOM:
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());}}}}}
}
注意.log行中的额外输出。没有其他更改,原始标记仍然使用。
输出
以下是新添加的代码的输出截图:
再次,如果你想自己尝试一下,只需打开this plnkr并看看控制台。
观察
打印出的DOM显示一些有趣的事:DOM在与pre-link阶段是不同的。
这里发生了什么 编译
我们已经知道在检测到DOM已经准备就绪时会处理DOM。
所以当开始遍历DOM,它碰到元素并且了解到它在匹配我们定义的指令它有额外的动作需要执行
由于在指令定义对象中定义了一个函数,他被调用并且相对应的元素DOM被当作参数传入
如果你仔细查看,在这点上,元素的DOM仍然是最初由浏览器使用原始HTML标记创建的DOM。
在中 DOM 常被指代为 ,因此我个人钟意使用 tElem作为参数名,以代表 。
一旦指令的函数已经运行,递归地便利深层的DOM并对和元素执行同样的编译步骤。
Post-link
在深入pre-link函数之前,让我们先看一看post-link函数
如果你创建的指令中只有link 函数,将它当作 post-link函数,这也是我们先讨论它的原因。
在向下传递DOM并运行所有编译之后,它再次反向遍历并运行所有相关的post-link函数。
DOM现在在相反的方向上遍历,因此post-link函数按相反的顺序调用。所以,虽然相反的顺序几分钟前看起来很奇怪,但它现在开始变得非常有意义。
这个相反的顺序保证了在父元素的post-link函数运行时所有子元素的post-link函数已经运行。
所以当的post-link执行时,我们保证 的post-link函数已经执行。
这就是为什么它被认为是最安全和默认的添加指令逻辑的原因。
但是元素的DOM呢?这里为什么不同?
一单已经调用了函数,它创建了一个对应模板元素的 并为提供一个scope。The scope可以为一个新的scope或者存在的一个,子scope或者隔离的scope,取决于相应指令定义对象的scope属性。
因此,在link发生时,实例元素和范围已经可用,并且它们被作为参数传递给post-link函数。
因此控制台输出有差异
Pre-link
在编写post-link函数时,可以保证所有子元素的post-link函数都已经执行。
在大多数情况下,这是非常有意义的,因此它是编写指令代码最常用的地方。
然而提供了一个附加的钩子,the pre-link函数,你可以在所有子元素执行post-link函数之前执行你的代码。
值得重申的是:
pre-link函数能保证在所有子 执行post-link前先执行。
因此,尽管post-link函数以相反的顺序调用非常合理,但现在再次以原始顺序调用所有pre-link函数是非常有意义的。
元素的pre-link函数被保障在任意子元素的pre-link与post-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
总结
回想起来,我们可以如下描述不同的功能和用例:
使用来实现在创建它的实例之前和创建范围之前更改原始DOM(模板元素)。
虽然可以有多个元素实例,但只有一个模板元素。ng-指令是这种情况的一个很好的例子。这使得编译功能成为修改DOM的理想场所,以后应该将其应用于所有实例,因为它只会运行一次,因此如果要删除大量实例,则会极大地提高性能。
模板元素和属性作为参数传递给编译函数,但没有scope可用:
/**
* Compile function
*
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(tElem, tAttrs){// ...};
Pre-link
使用pre-link函数来实现当已经编译子元素时,但在子元素的任何post-link函数被调用之前运行的逻辑。
scope, 和属性作为参数传递给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){// ...};
Here you can see code of that use a pre-link . Post-link
使用post-link函数执行逻辑,所有的子元素已经被编译并且所有子元素的pre-link post-link已经执行。
所以post-link是被当作最安全和默认的位置放置你的代码。
scope, 和属性作为参数传递给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){// ...};
最后
如有任何问题和建议欢迎发送至邮箱讨论:
编写不易,若您觉得对您有帮助,欢迎打赏