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

2022-08-30 01:13:58

有人可以用简单的术语解释吗?

文档似乎有点迟钝。我没有得到何时使用一个的本质和大局。对比两者的例子会很棒。


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

  • link函数 - 用于注册 DOM 侦听器(即,实例作用域上的$watch表达式)以及实例 DOM 操作(即,iElement 的操作 = 单个实例元素)。
    它在克隆模板后执行。例如,在<li ng-repeat...>,在为该特定<li>元素克隆(到 iElement 中)<li>模板 (tElement) 后,将执行 link 函数。
    $watch() 允许指令在实例范围属性更改时收到通知(实例范围与每个实例相关联),这允许指令通过将内容从实例范围复制到 DOM 中来向 DOM 呈现更新的实例值。

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

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

帮助确定要使用的一种方法:考虑编译函数不接收参数。(我故意忽略了 transclude 链接函数参数,它接收一个 transcluded 作用域 - 这很少使用。因此,编译函数无法执行任何需要(实例)作用域的操作 - 您无法$watch任何模型/实例作用域属性,不能使用实例作用域信息操作 DOM,不能调用在实例作用域上定义的函数,等等。scope

但是,编译函数(如链接函数)确实可以访问属性。因此,如果您的 DOM 操作不需要实例作用域,则可以使用编译函数。下面是一个仅使用编译函数的指令示例,出于这些原因。它会检查属性,但不需要实例范围来完成其工作。

下面是一个也只使用编译函数的指令示例。该指令只需要转换模板 DOM,因此可以使用编译函数。

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

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

请注意,如果需要编译函数和链接函数(或链接前和链接后函数),编译函数必须返回链接函数,因为如果定义了“编译”属性,则会忽略“链接”属性。

另请参见


答案 2

我在这个问题上用头撞了几天,我觉得需要更多的解释。

基本上,文档提到分离在很大程度上是一种性能增强。我想重申的是,编译阶段主要用于在编译子元素本身之前需要修改 DOM 的情况。

出于我们的目的,我将强调术语,否则会令人困惑:

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

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

关于编译函数的一些说明:

  1. 您无法修改 ROOT 元素(指令影响的元素),因为它已经从 DOM 的外部级别进行编译(编译服务已经扫描了该元素上的指令)。

  2. 如果要向(嵌套)元素添加其他指令,请执行下列操作之一:

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

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

了解嵌套和对$compile显式调用的工作原理也很有帮助,因此我创建了一个游乐场,用于在 http://jsbin.com/imUPAMoV/1/edit 查看它。基本上,它只是将步骤记录到控制台.log。

我将在这里陈述您在该箱中看到的结果。对于嵌套的自定义指令 tp 和 sp 的 DOM,如下所示:

<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 后链接函数,它显式调用第三个指令 (up) 上的编译 SERVICE,该指令在最后执行所有三个步骤。

现在,我想演练几个场景,以展示如何使用编译和链接来执行各种操作:

方案 1:作为宏的指令

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

假设您有一个指向以下内容的模板 Url:

<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. 它在指令上调用编译函数,该指令更新 DOM。
  3. 然后,编译服务进入生成的 DOM,并编译(递归)
  4. 然后,编译服务调用预链接自上而下
  5. 然后,编译服务调用链接后自下而上,因此 my-field 的链接函数在内部节点链接后调用。

在上面的示例中,不需要链接,因为指令的所有工作都是在编译函数中完成的。

在任何时候,指令中的代码都可以要求编译器 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()创建了它们),那么最终结果与以前几乎相同(尽管您可能正在重复一些工作)。但是,如果元素上有其他指令,您只是导致再次处理这些指令,这可能会导致各种不稳定的行为(例如,事件和监视的双重注册)。

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

方案 2:通过作用域数据进行 DOM 配置

这个是从上面的例子中得出的。假设在操作 DOM 时需要访问作用域。好吧,在这种情况下,编译部分对您毫无用处,因为它发生在作用域可用之前。

因此,假设您希望使用验证来拉皮条化输入,但您希望从服务器端 ORM 类 (DRY) 导出验证,并让它们自动应用并为这些验证生成正确的客户端 UI。

您的模型可能会推动:

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>
...

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

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

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

这样:

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中提取该数据的需求(提示:延迟编译)。

场景三:通过链路进行双向数据绑定

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