angularjs中的编译和链接函数有什么区别

时间:2012-08-28 17:05:25

标签: javascript angularjs

有人可以用简单的语言解释一下吗?

文档看起来有点迟钝。我没有得到何时使用一个而不是另一个的本质和全局。两个对比的例子很棒。

7 个答案:

答案 0 :(得分:217)

  • 编译函数 - 用于模板 DOM操作(即操作tElement = template元素),因此适用于与指令关联的模板的所有DOM克隆的操作。

  • 链接函数 - 用于注册DOM侦听器(即实例范围上的$ watch表达式)以及实例 DOM操作(即,操作iElement =单个实例元素)。
    它在克隆模板后执行。例如,在< ling-repeat ...>内,在< li>之后执行链接功能。模板(tElement)已被克隆(进入iElement),用于该特定的< li> 。$ A watch()允许指令通知实例范围属性更改(实例范围与每个实例关联),这允许指令将更新的实例值呈现给DOM - 通过复制从实例范围到DOM的内容。

请注意,DOM转换可以在编译功能和/或链接功能中完成。

大多数指令只需要一个链接函数,因为大多数指令只处理特定的DOM元素实例(及其实例范围)。

帮助确定使用哪种方法的一种方法:考虑编译函数没有收到scope参数。 (我故意忽略了transclude链接函数参数,它接收了一个被转换的范围 - 这是rarely使用的。)所以编译函数不能做任何你想做的事情,需要一个(实例)范围 - 你不能$ watch任何模型/实例范围属性,你不能使用实例范围信息操纵DOM,你不能调用实例范围上定义的函数等。

但是,编译功能(如链接功能)可以访问属性。因此,如果您的DOM操作不需要实例范围,则可以使用编译功能。由于这些原因,这里是仅使用编译函数的指令的an example。它检查属性,但它不需要实例范围来完成它的工作。

这是指令的an example,它也只使用编译功能。该指令只需要转换模板DOM,因此可以使用编译函数。

另一种帮助确定使用哪种方法的方法:如果你不在链接函数中使用“element”参数,那么你可能不需要链接函数。

由于大多数指令都有链接功能,我不打算提供任何示例 - 它们应该很容易找到。

请注意,如果您需要编译函数和链接函数(或前后链接函数),则编译函数必须返回链接函数,因为如果'compile'属性是,则忽略'link'属性定义

另见

答案 1 :(得分:70)

我把头撞到了墙上几天,我觉得还有更多的解释。

基本上,文档提到分离主要是性能增强。我要重申,编译阶段主要用于需要在编译子元素之前修改DOM。

出于我们的目的,我会强调术语,否则会引起混淆:

编译器SERVICE($ compile)是一种角度机制,它处理DOM并在指令中运行各种代码。

编译FUNCTION是指令中的一位代码,它在特定时间由编译器SERVICE($ compile)运行。

关于编译功能的一些注意事项:

  1. 您无法修改ROOT元素(您的指令影响的元素),因为它已经从DOM的外层编译(编译SERVICE已经扫描了该元素的指令)。

  2. 如果要将其他指令添加到(嵌套)元素,您可以:

    1. 必须在编译阶段添加它们。

    2. 必须将编译服务注入链接阶段并手动编译元素。但是,要小心两次编译!

  3. 查看$ compile的嵌套和显式调用是如何工作的也很有帮助,所以我创建了一个可以在http://jsbin.com/imUPAMoV/1/edit查看的操场。基本上,它只是将步骤记录到console.log。

    我将在此处说明您在该垃圾箱中看到的结果。对于自定义指令的DOM,tp和sp嵌套如下:

    <tp>
       <sp>
       </sp>
    </tp>
    

    Angular compile SERVICE将调用:

    tp compile
    sp compile
    tp pre-link
    sp pre-link
    sp post-link
    tp post-link
    

    jsbin代码还有tp post-link FUNCTION在第三个指令(up)上显式调用compile SERVICE,最后执行所有三个步骤。

    现在,我想通过几个场景来展示如何使用编译和链接来做各种事情:

    情景1:作为MACRO的指令

    您希望动态地将指令(例如ng-show)添加到模板中可以从属性派生的内容中。

    假设您有一个指向:

    的templateUrl
    <div><span><input type="text"></span><div>
    

    你需要一个自定义指令:

    <my-field model="state" name="address"></my-field>
    

    将DOM转换为:

    <div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>
    

    基本上,您希望通过使用指令可以解释的一致模型结构来减少样板。换句话说:你想要一个宏。

    这对于编译阶段非常有用,因为您可以根据属性将所有DOM操作基于您所知道的事情。只需使用jQuery添加属性:

    compile: function(tele, tattr) {
       var span = jQuery(tele).find('span').first();
       span.attr('ng-show', tattr.model + ".visible." + tattr.name);
       ...
       return { 
         pre: function() { },
         post: function() {}
       };
    }
    

    操作顺序是(你可以通过前面提到的jsbin看到这个):

    1. 编译服务查找my-field
    2. 它调用指令上的编译FUNCTION,它更新DOM。
    3. 然后编译SERVICE进入生成的DOM,然后进入COMPILES(递归)
    4. 编译SERVICE然后调用pre-link top-down
    5. 编译服务然后调用后链接BOTTOM UP,因此在链接内部节点后调用my-field的链接功能。
    6. 在上面的例子中,不需要链接,因为所有指令的工作都是在编译功能中完成的。

      在任何时候,指令中的代码都可以要求编译器SERVICE在其他元素上运行。

      这意味着如果您注入编译服务,我们可以在链接函数中执行完全相同的操作:

      directive('d', function($compile) {
        return {
          // REMEMBER, link is called AFTER nested elements have been compiled and linked!
          link: function(scope, iele, iattr) {
            var span = jQuery(iele).find('span').first();
            span.attr('ng-show', iattr.model + ".visible." + iattr.name);
            // CAREFUL! If span had directives on it before
            // you will cause them to be processed again:
            $compile(span)(scope);
          }
      });
      

      如果您确定要传递给$ compile SERVICE的元素最初是无指令的(例如,它们来自您定义的模板,或者您刚刚使用angular.element()创建它们),那么最终结果与以前几乎相同(尽管你可能会重复一些工作)。但是,如果元素上有其他指令,则只会导致再次处理这些指令,这会导致各种不稳定的行为(例如事件和手表的双重注册)。

      因此,编译阶段是宏观工作的更好选择。

      SCENARIO 2:通过范围数据进行DOM配置

      这一点来自上面的例子。假设您在操作DOM时需要访问作用域。那么,在这种情况下,编译部分对你来说是无用的,因为它发生在范围可用之前。

      所以,让我们假设您想要使用验证来输出输入,但是您希望从服务器端ORM类(DRY)导出验证,并让它们自动应用并生成正确的客户端这些验证的用户界面。

      你的模特可能会推动:

      scope.metadata = {
        validations: {
           address: [ {
             pattern: '^[0-9]',
             message: "Address must begin with a number"
           },
           { maxlength: 100,
             message: "Address too long"
           } ]
        }
      };
      scope.state = {
        address: '123 Fern Dr'
      };
      

      你可能想要一个指令:

      <form name="theForm">
        <my-field model="state" metadata="metadata" name="address">
      </form>
      

      自动包含正确的指令和div以显示各种验证错误:

      <form name="theForm">
        <div>
          <input ng-model="state.address" type="text">
          <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
      ...
      

      在这种情况下,您肯定需要访问范围(因为这是存储验证的地方),并且必须手动编译添加内容,再次注意不要双重编译。 (作为旁注,您需要在包含的表单标记上设置一个名称(我在这里假设该表格),并且可以通过链接访问iElement.parent()。controller(&#39; form&#) 39;)$名)

      在这种情况下,编写编译函数没有意义。链接真的是你想要的。步骤将是:

      1. 定义一个完全没有角度指令的模板。
      2. 定义添加各种属性的链接功能
      3. 删除您在顶级元素(my-field指令)上可能允许的任何角度指令。它们已经过处理,这是一种防止它们被双重处理的方法。
      4. 通过调用顶级元素
      5. 上的编译SERVICE来完成

        像这样:

        angular.module('app', []).
        directive('my-field', function($compile) {
          return {
            link: function(scope, iele, iattr) {
              // jquery additions via attr()
              // remove ng attr from top-level iele (to avoid duplicate processing)
              $compile(iele)(scope); // will pick up additions
            }
          };
        });
        

        当然,您可以逐个编译嵌套元素,以避免在再次编译顶级元素时担心ng指令的重复处理。

        关于这种情况的最后一点说明:我暗示你要从服务器推送验证的定义,在我的例子中,我已将它们显示为范围内的数据。我把它作为练习让读者弄清楚如何处理需要从REST API中提取数据的方法(提示:延迟编译)。

        情景3:通过链接进行双向数据绑定

        当然,链接最常见的用途是通过watch / apply简单地连接双向数据绑定。大多数指令属于这一类,因此在其他地方已经充分涵盖。

答案 2 :(得分:50)

From the docs:

  

编译器

     

编译器是一种角度服务,它遍历DOM以查找属性。编译过程分为两个阶段。

     
      
  1. 编译:遍历DOM并收集所有指令。结果是一个链接功能。

  2.   
  3. 链接:将指令与范围相结合并生成实时视图。范围模型中的任何更改都会反映在视图中,并且与视图的任何用户交互都会反映在范围模型中。使范围模型成为事实的唯一来源。

  4.         

    某些指令(如ng-repeat)为集合中的每个项目克隆DOM元素一次。编译和链接阶段可以提高性能,因为克隆模板只需要编译一次,然后为每个克隆实例链接一次。

因此,至少在某些情况下,这两个阶段作为优化单独存在。


From @UmurKontacı

  

如果要进行DOM转换,则应为compile。如果您想添加一些行为更改的功能,则应该在link

答案 3 :(得分:18)

这是来自Misko关于指令的谈话。 http://youtu.be/WqmeI5fZcho?t=16m23s

  

将编译器功能视为   在模板上工作的东西和允许的东西   例如,通过向其添加类来更改模板本身   这样的事情。但它实际上是连接功能   因为链接功能具有将两者绑定在一起的工作   访问范围,它是执行一次的链接功能   对于特定模板的每个实例化。所以唯一的一种   你可以在编译函数中放置的东西就是那些东西   在所有情况下都很常见。

答案 4 :(得分:10)

线程迟到了。但是,为了未来读者的利益:

我看到了以下视频,它以非常好的方式解释了Angular JS中的编译和链接:

https://www.youtube.com/watch?v=bjFqSyddCeA

在此处复制/输入所有内容并不令人满意。我从视频中截取了几个屏幕截图,它解释了编译和链接阶段的每个阶段:

Compile and Link in Angular JS

Compile and Link in Angular JS - Nested Directives

第二个截图有点令人困惑。但是,如果我们按照步骤编号进行操作,那就很简单了。

第一个周期:&#34;编译&#34;首先对所有指令执行 第二周期:&#34;控制器&#34;和&#34; Pre-Link&#34;得到执行(只是一个接一个) 第三个周期:&#34;后链接&#34;以相反的顺序执行(从最里面开始)

以下是代码,演示了上述内容:

var app = angular.module('app', []);

app.controller('msg', ['$scope', function($scope){

}]);

app.directive('message', function($interpolate){
    return{

        compile: function(tElement, tAttributes){ 
            console.log(tAttributes.text + " -In compile..");
            return {

                pre: function(scope, iElement, iAttributes, controller){
                    console.log(iAttributes.text + " -In pre..");
                },

                post: function(scope, iElement, iAttributes, controller){
                    console.log(iAttributes.text + " -In Post..");
                }

            }
        },

        controller: function($scope, $element, $attrs){
            console.log($attrs.text + " -In controller..");
        },

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

<强>更新

同一视频的第2部分可在此处获取:https://www.youtube.com/watch?v=1M3LZ1cu7rw该视频在一个简单示例中解释了有关如何在Angular JS的编译和链接过程中修改DOM和处理事件的更多信息。

答案 5 :(得分:6)

两个阶段:编译和链接

编译:

遍历DOM树,查找指令(元素/属性/类/注释)。指令的每个编译都可以修改其模板,或修改尚未编译的内容。一旦指令匹配,它就会返回一个链接函数,该函数在稍后阶段用于将元素链接在一起。在编译阶段结束时,我们有一个已编译的指令列表及其相应的链接函数。

链接:

当链接元素时,DOM树在DOM树的分支点处被破坏,并且内容被模板的已编译(和链接)实例替换。原始置换内容要么被丢弃,要么在转换的情况下重新链接回模板。通过翻转,两个部分连接在一起(有点像链,模板片位于中间)。调用链接函数时,模板已绑定到范围,并添加为元素的子元素。链接功能是您进一步操作DOM并设置更改侦听器的机会。

答案 6 :(得分:3)

这个问题已经过时了,我想做一些可能会有所帮助的简短摘要:

  • 编译为所有指令实例调用一次
  • 编译的主要目的是返回/创建链接(以及可能的前/后)功能/对象。您还可以初始化在指令实例之间共享的内容。
  • 在我看来,&#34;链接&#34;这个功能是一个令人困惑的名称。我希望&#34;预渲染&#34;。
  • 为每个指令实例调用
  • 链接,其目的是准备在DOM中呈现指令。