天天看點

AngularJS指令中的compile與link函數解析 AngularJS指令中的compile與link函數解析

AngularJS指令中的compile與link函數解析

通常大家在使用

ng

中的指令的時候,用的連結函數最多的是

link

屬性,下面這篇文章将告訴大家

complie

,

pre-link

,

post-link

的用法與差別.

原文位址

angularjs

裡的指令非常神奇,允許你建立非常語義化以及高度重用的元件,可以了解為

web components

的先驅者.

網上已經有很多介紹怎麼使用指令的文章以及相關書籍,互相比較的話,很少有介紹

compile

link

的差別,更别說

pre-link

post-link

了.

大部分教程隻是簡單的說下

compile

會在

ng

内部用到,而且建議大家隻用

link

屬性,大部分指令的例子裡都是這樣的

這是非常不幸的,因為正确的了解這些函數的差別會提高你對

ng

内部運作機理的了解,有助于你開發更好的自定義指令.

是以跟着我一起來看下面的内容一步步的去了解這些函數是什麼以及它們應該在什麼時候用到

  • 本文假設你已經對指令有一定的了解了,如果沒有的話強烈建議你看看這篇文章AngularJS developer guide section on directives

NG中是怎麼樣處理指令的

開始分析之前,先讓我們看看

ng

中是怎麼樣處理指令的.

當浏覽器渲染一個頁面時,本質上是讀

html

辨別,然後建立

dom

節點,當

dom

樹建立完畢之後廣播一個事件給我們.

當你在頁面中使用

script

标簽加載

ng

應用程式代碼時,

ng

監聽上面的

dom

完成事件,查找帶有

ng-app

屬性的元素.

當找到這樣的元素之後,

ng

開始處理

dom

以這個元素的起點,是以假如

ng-app

被添加到

html

元素上,則

ng

就會從

html

元素開始處理

dom

.

從這個起點開始,

ng

開始遞歸查找所有子元素裡面,符合應用程式裡定義好的指令規則.

ng

怎樣處理指令其實是依賴于它定義時的對象屬性的,你可以定義一個

compile

或者一個

link

函數,或者用

pre-link

post-link

函數來代替

link

.

是以這些函數的差別呢?為什麼要使用它?以及什麼時候使用它呢?

帶着這些問題跟着我一步一步來解答這些迷團吧

一段代碼

為了解釋這些函數的差別,下面我将使用一個簡單易懂的例子

  • 如果您有任何的問題,請不要猶豫趕緊在下面加上你的評論吧.

看看下面一段

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

在處理指令時,内部的流程

代碼輸出

下面是一個在控制台輸出結果的截圖

AngularJS指令中的compile與link函數解析 AngularJS指令中的compile與link函數解析

如果想自己試一下這個例子的話,請點選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

标記基本沒别的改變.

這個應該能夠加深我們對于這些函數上下文的了解.

再次運作代碼看看

輸出

下面是一個在控制台輸出結果的截圖

AngularJS指令中的compile與link函數解析 AngularJS指令中的compile與link函數解析

假如你還想自己運作看看效果,可以點選this plnkr,然後在控制台裡檢視輸出結果.

觀察

輸出

dom

的結果可以暴露一些有趣的事情:

dom

内容在

compile

pre-link

兩個函數中是不一樣的

是以發生了什麼呢?

Compile

我們已經知道當

ng

發現

dom

建構完成時就開始處理

dom

.

是以當

ng

在周遊

dom

的時候,碰到

level-one

元素,從它的定義那裡了解到,要執行一些必要的函數

因為

compile

函數定義在

level-one

指令的指令對象裡,是以它會被調用并傳遞一個

element

對象作為它的參數

如果你仔細觀察,就會看到,浏覽器建立這個

element

對象時,仍然是最原始的

html

标記

  • ng

    中,原始

    dom

    通常用來辨別

    template element

    ,是以我在定義

    compile

    函數參數時就用到了

    tElem

    名字,這個變量指向的就是

    template element

    .

一旦運作

levelone

指令中的

compile

函數,

ng

就會遞歸深度周遊它的

dom

節點,然後在

level-two

level-three

上面重複這些操作.

Post-link

深入了解

pre-link

函數之前,讓我們來看看

post-link

函數.

  • 如果你在定義指令的時候隻使用了一個

    link

    函數,那麼

    ng

    會把這個函數當成

    post-link

    來處理,是以我們要先讨論這個函數

ng

周遊完所有的

dom

并運作完所有的

compile

函數之後,就反向調用相關聯的

post-link

函數.

dom

現在開始反向,并執行

post-link

函數,是以,在之前這種反向的調用看起來有點奇怪,其實這樣做是非常有意義的.

AngularJS指令中的compile與link函數解析 AngularJS指令中的compile與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

函數的參數清單中去.

  • 我個人總是使用

    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

運作之前執行的.見下圖

AngularJS指令中的compile與link函數解析 AngularJS指令中的compile與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

原生指令的工作原理,也能幫你優化你自己的自定義指令.

如果還有問題的話,歡迎大家在下面評論裡加上你的問題

以後還會接着分析關于指令裡的其它兩個問題:

  • 指令使用

    transclusion

    屬性是怎麼工作的?
  • 指令的

    controller

    函數是怎麼關聯的

最後,如果發現本文哪裡有不對的,請及時給我發評論

謝謝