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原生指令的工做原理,也能幫你優化你本身的自定義指令.

若是還有問題的話,歡迎你們在下面評論里加上你的問題

 

謝謝!

相關文章
相關標籤/搜索