angularjs指令中的compile详解

篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉你们complie,pre-link,post-link的用法与区别等内容,须要的朋友能够参考下
 

一般你们在使用ng中的指令的时候,用的连接函数最多的是link属性,下面这篇文章将告诉你们complie,pre-link,post-link的用法与区别.html

angularjs里的指令很是神奇,容许你建立很是语义化以及高度重用的组件,能够理解为web components的先驱者.angularjs

网上已经有不少介绍怎么使用指令的文章以及相关书籍,相互比较的话,不多有介绍compile与link的区别,更别说pre-link与post-link了.web

大部分教程只是简单的说下compile会在ng内部用到,并且建议你们只用link属性,大部分指令的例子里都是这样的浏览器

这是很是不幸的,由于正确的理解这些函数的区别会提升你对ng内部运行机理的理解,有助于你开发更好的自定义指令.安全

因此跟着我一块儿来看下面的内容一步步的去了解这些函数是什么以及它们应该在何时用到app

本文假设你已经对指令有必定的了解了,若是没有的话强烈建议你看看这篇文章AngularJS developer guide section on directivesdom

 NG中是怎么样处理指令的ide

开始分析以前,先让咱们看看ng中是怎么样处理指令的.函数

当浏览器渲染一个页面时,本质上是读html标识,而后创建dom节点,当dom树建立完毕以后广播一个事件给咱们.post

当你在页面中使用script标签加载ng应用程序代码时,ng监听上面的dom完成事件,查找带有ng-app属性的元素.

当找到这样的元素以后,ng开始处理dom以这个元素的起点,因此假如ng-app被添加到html元素上,则ng就会从html元素开始处理dom.

从这个起点开始,ng开始递归查找全部子元素里面,符合应用程序里定义好的指令规则.

ng怎样处理指令实际上是依赖于它定义时的对象属性的,你能够定义一个compile或者一个link函数,或者用pre-link和post-link函数来代替link.

因此这些函数的区别呢?为何要使用它?以及何时使用它呢?

带着这些问题跟着我一步一步来解答这些迷团吧

一段代码

为了解释这些函数的区别,下面我将使用一个简单易懂的例子

1.若是您有任何的问题,请不要犹豫赶忙在下面加上你的评论吧.

看看下面一段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标记

1.在ng中,原始dom一般用来标识template element,因此我在定义compile函数参数时就用到了tElem名字,这个变量指向的就是template element.

一旦运行levelone指令中的compile函数,ng就会递归深度遍历它的dom节点,而后在level-two与level-three上面重复这些操做.

Post-link

深刻了解pre-link函数以前,让咱们来看看post-link函数.

2.若是你在定义指令的时候只使用了一个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函数的参数列表中去.

1.我我的老是使用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原生指令的工做原理,也能帮你优化你本身的自定义指令.

若是还有问题的话,欢迎你们在下面评论里加上你的问题

 

谢谢!