angularjs中的compile函数和link函数有什么区别

有人能简单解释一下吗?

文档看起来有点迟钝。我没有领会到什么时候该用一种而不是另一种的本质和大局。一个对比这两者的例子会很棒。

92591 次浏览

从文档中:

编译器

Compiler是一个angular服务,它遍历DOM寻找属性。编译过程分为两个阶段。

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

  2. 将指令与作用域结合并生成实时视图。作用域模型中的任何更改都反映在视图中,用户与视图的任何交互都反映在作用域模型中。使范围模型成为事实的单一来源。

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

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


从@UmurKontacı:

如果你要进行DOM转换,它应该是compile。如果你想添加一些行为变化的特性,它应该在link中。

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

  • link函数-用于注册DOM监听器(即实例作用域中的$watch表达式)以及instance DOM操作(即iElement =单个实例元素的操作)。
    模板克隆完成后执行。例如,在一个<li ng-repeat…>,链接函数是在<li>模板(tElement)为特定的<li>元素克隆(到一个iElement)之后执行的。$watch()允许指令得到实例作用域属性变化的通知(实例作用域与每个实例相关联),这允许指令将更新后的实例值呈现给DOM——通过将内容从实例作用域复制到DOM。

注意,DOM转换可以在compile函数和/或link函数中完成。

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

一种帮助确定使用哪种方法的方法是:考虑compile函数没有接收scope参数。(我故意忽略了transclude链接函数参数,它接收一个transclude作用域——这是使用的很少。)因此compile函数不能做任何需要(实例)作用域的事情——你不能$watch任何模型/实例作用域属性,你不能使用实例作用域信息操纵DOM,你不能调用实例作用域上定义的函数,等等。

但是,compile函数(像link函数一样)可以访问属性。因此,如果DOM操作不需要实例作用域,则可以使用compile函数。由于这些原因,这里是一个指令的一个例子,它只使用编译函数。它检查属性,但是不需要实例作用域来完成它的工作。

这里是一个指令的一个例子,它也只使用编译函数。该指令只需要转换模板DOM,因此可以使用compile函数。

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

因为大多数指令都有一个链接函数,所以我不打算提供任何示例——它们应该很容易找到。

注意,如果你需要一个编译函数和一个链接函数(或前和后链接函数),编译函数必须返回一个或多个链接函数,因为如果定义了'compile'属性,'link'属性将被忽略。

另请参阅

这是Misko关于指令的演讲。http://youtu.be/WqmeI5fZcho?t=16m23s

将编译器函数看作 在模板上工作的东西和被允许的东西 更改模板本身,例如,向模板中添加类或 诸如此类。但真正起作用的是连接函数 把两者结合在一起的功因为连接函数 访问作用域,它是执行一次的链接函数 对于特定模板的每个实例化。所以唯一的一种 你可以放在编译函数里面的东西是

我在这个问题上绞尽脑汁了好几天,我觉得应该再多解释一下。

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

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

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

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

关于compile FUNCTION的一些注意事项:

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

  2. 如果你想在(嵌套的)元素中添加其他指令,你可以:

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

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

    3. 李< / ol > < / >

    它还有助于了解对$compile的嵌套和显式调用是如何工作的,因此我在http://jsbin.com/imUPAMoV/1/edit中创建了一个用于查看的游乐场。基本上,它只是将步骤记录到console.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 post-link FUNCTION在第三个指令(up)上显式调用compile SERVICE,该指令在最后完成所有三个步骤。

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

    场景1:指令作为宏

    你想动态地添加一个指令(比如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. compile SERVICE找到my-field
    2. 它调用指令上的compile FUNCTION来更新DOM。
    3. 然后,编译服务进入结果DOM,然后编译(递归地)
    4. 然后,compile SERVICE调用预链接自顶向下
    5. 然后compile SERVICE调用post-link BOTTOM - UP,因此my-field的link函数在内部节点链接之后调用。

    在上面的例子中,不需要链接,因为所有指令的工作都是在compile FUNCTION中完成的。

    在任何时候,指令中的代码都可以要求编译器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),并且可以通过element .parent().controller('form').$name链接访问它)。

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

    1. 定义一个完全没有angular指令的模板。
    2. 定义一个添加各种属性的链接函数
    3. 移除所有允许在顶层元素上使用的angular指令(my-field指令)。它们已经被处理过了,这是一种防止它们被重复处理的方法。
    4. 通过在顶级元素上调用compile 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:通过链接进行双向数据绑定

    当然,link最常见的用法是通过watch/apply简单地连接双向数据绑定。大多数指令都属于这一类,所以在其他地方有充分的介绍。

两个阶段:编译和链接

编译:

遍历DOM树寻找指令(元素/属性/类/注释)。指令的每次编译都可以修改它的模板,或者修改它还没有编译的内容。一旦一个指令被匹配,它就返回一个链接函数,这个函数在后面的阶段用于将元素链接在一起。在编译阶段的最后,我们有一个已编译指令及其对应的链接函数列表。

链接:

当一个元素被链接时,DOM树在它在DOM树中的分支点被破坏,内容被模板的编译(并链接)实例所取代。原始移位的内容要么被丢弃,要么在传输的情况下重新链接回模板。通过透射,两个片段被链接在一起(有点像一个链条,模板片段在中间)。当link函数被调用时,模板已经被绑定到一个作用域,并作为元素的子元素添加。link函数是您进一步操作DOM和设置更改侦听器的机会。

有点晚了。但是,为了方便将来的读者:

我偶然看到下面这个视频,它以一种非常棒的方式解释了Angular JS中的编译和链接:

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

复制/输入这里的所有内容不太令人愉快。我从视频中截取了几个截图,解释了编译和链接阶段的每个阶段:

编译和链接在Angular JS

编译和链接在Angular JS -嵌套指令

第二张截图有点让人困惑。但是,如果我们按照步长编号,就很简单了。

第一个循环:"Compile"首先在所有指令上执行 第二个循环:执行“Controller”和“Pre-Link”(只是一个接一个) 第三个循环:“Post-Link”以相反的顺序执行(从最内层开始)

下面是代码,它演示了上面的内容:

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>

更新:

该视频通过一个简单的例子解释了如何在Angular JS的编译和链接过程中修改DOM和处理事件。

这个问题太老了,我想做一个简短的总结,这可能会有所帮助:

  • 编译为所有指令实例调用一次
  • 编译的主要目的是返回/创建链接(可能是前/后)函数/对象。你也可以init指令实例间共享的东西。
  • 在我看来,“链接”是这个功能的一个令人困惑的名字。我更喜欢“预渲染”。
  • link为每个指令实例调用,它的目的是准备在DOM中呈现指令。