angularjs指令中的compile與link函數(shù)詳解
通常大家在使用ng中的指令的時候,用的鏈接函數(shù)最多的是link屬性,下面這篇文章將告訴大家complie,pre-link,post-link的用法與區(qū)別.
angularjs里的指令非常神奇,允許你創(chuàng)建非常語義化以及高度重用的組件,可以理解為web components的先驅(qū)者.
網(wǎng)上已經(jīng)有很多介紹怎么使用指令的文章以及相關書籍,相互比較的話,很少有介紹compile與link的區(qū)別,更別說pre-link與post-link了.
大部分教程只是簡單的說下compile會在ng內(nèi)部用到,而且建議大家只用link屬性,大部分指令的例子里都是這樣的
這是非常不幸的,因為正確的理解這些函數(shù)的區(qū)別會提高你對ng內(nèi)部運行機理的理解,有助于你開發(fā)更好的自定義指令.
所以跟著我一起來看下面的內(nèi)容一步步的去了解這些函數(shù)是什么以及它們應該在什么時候用到
本文假設你已經(jīng)對指令有一定的了解了,如果沒有的話強烈建議你看看這篇文章AngularJS developer guide section on directives
NG中是怎么樣處理指令的
開始分析之前,先讓我們看看ng中是怎么樣處理指令的.
當瀏覽器渲染一個頁面時,本質(zhì)上是讀html標識,然后建立dom節(jié)點,當dom樹創(chuàng)建完畢之后廣播一個事件給我們.
當你在頁面中使用script標簽加載ng應用程序代碼時,ng監(jiān)聽上面的dom完成事件,查找?guī)в衝g-app屬性的元素.
當找到這樣的元素之后,ng開始處理dom以這個元素的起點,所以假如ng-app被添加到html元素上,則ng就會從html元素開始處理dom.
從這個起點開始,ng開始遞歸查找所有子元素里面,符合應用程序里定義好的指令規(guī)則.
ng怎樣處理指令其實是依賴于它定義時的對象屬性的,你可以定義一個compile或者一個link函數(shù),或者用pre-link和post-link函數(shù)來代替link.
所以這些函數(shù)的區(qū)別呢?為什么要使用它?以及什么時候使用它呢?
帶著這些問題跟著我一步一步來解答這些迷團吧
一段代碼
為了解釋這些函數(shù)的區(qū)別,下面我將使用一個簡單易懂的例子
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'));
結(jié)果非常簡單:讓ng來處理三個嵌套指令,并且每個指令都有自己的complile,pre-link,post-link函數(shù),每個函數(shù)都會在控制臺里打印一行東西來標識自己.
這個例子能夠讓我們簡單的了解到ng在處理指令時,內(nèi)部的流程
代碼輸出
下面是一個在控制臺輸出結(jié)果的截圖
如果想自己試一下這個例子的話,請點擊this plnkr,然后在控制臺查看結(jié)果.
分析代碼
第一個要注意的是這些函數(shù)的調(diào)用順序:
// 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的執(zhí)行順序是依次執(zhí)行的,但是post-link正好相反.
所以上面已經(jīng)明確標識出了不同的階段,但是compile與pre-link有什么區(qū)別呢,都是相同的執(zhí)行順序,為什么還要分成兩個不同的函數(shù)呢?
DOM
為了挖的更深一點,讓我們簡單的修改一下上面的代碼,它也會在各個函數(shù)里打印參數(shù)列表中的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標記基本沒別的改變.
這個應該能夠加深我們對于這些函數(shù)上下文的理解.
再次運行代碼看看
輸出
下面是一個在控制臺輸出結(jié)果的截圖
假如你還想自己運行看看效果,可以點擊this plnkr,然后在控制臺里查看輸出結(jié)果.
觀察
輸出dom的結(jié)果可以暴露一些有趣的事情:dom內(nèi)容在compile與pre-link兩個函數(shù)中是不一樣的
所以發(fā)生了什么呢?
Compile
我們已經(jīng)知道當ng發(fā)現(xiàn)dom構(gòu)建完成時就開始處理dom.
所以當ng在遍歷dom的時候,碰到level-one元素,從它的定義那里了解到,要執(zhí)行一些必要的函數(shù)
因為compile函數(shù)定義在level-one指令的指令對象里,所以它會被調(diào)用并傳遞一個element對象作為它的參數(shù)
如果你仔細觀察,就會看到,瀏覽器創(chuàng)建這個element對象時,仍然是最原始的html標記
1.在ng中,原始dom通常用來標識template element,所以我在定義compile函數(shù)參數(shù)時就用到了tElem名字,這個變量指向的就是template element.
一旦運行l(wèi)evelone指令中的compile函數(shù),ng就會遞歸深度遍歷它的dom節(jié)點,然后在level-two與level-three上面重復這些操作.
Post-link
深入了解pre-link函數(shù)之前,讓我們來看看post-link函數(shù).
2.如果你在定義指令的時候只使用了一個link函數(shù),那么ng會把這個函數(shù)當成post-link來處理,因此我們要先討論這個函數(shù)
當ng遍歷完所有的dom并運行完所有的compile函數(shù)之后,就反向調(diào)用相關聯(lián)的post-link函數(shù).
dom現(xiàn)在開始反向,并執(zhí)行post-link函數(shù),因此,在之前這種反向的調(diào)用看起來有點奇怪,其實這樣做是非常有意義的.
當運行包含子指令的指令post-link時,反向的post-link規(guī)則可以保證它的子指令的post-link是已經(jīng)運行過的.
所以,當運行l(wèi)evel-one指令的post-link函數(shù)的時候,我們能夠保證level-two和level-three的post-link其實都已經(jīng)運行過了.
這就是為什么人們都認為post-link是最安全或者默認的寫業(yè)務邏輯的地方.
但是為什么這里的element跟compile里的又不同呢?
一旦ng調(diào)用過指令的compile函數(shù),就會創(chuàng)建一個template element的element實例對象,并且為它提供一個scope對象,這個scope有可能是新實例,也有可能是已經(jīng)存在,可能是個子scope,也有可能是獨立的scope,這些都得依賴指令定義對象里的scope屬性值
所以當linking發(fā)生時,這個實例element以及scope對象已經(jīng)是可用的了,并且被ng作為參數(shù)傳遞到post-link函數(shù)的參數(shù)列表中去.
1.我個人總是使用iElem名稱來定義一個link函數(shù)的參數(shù),并且它是指向element實例的
所以post-link(pre-link)函數(shù)的element參數(shù)對象是一個element實例而不是一個template element.
所以上面例子里的輸出是不同的
Pre-link
當寫了一個post-link函數(shù),你可以保證在執(zhí)行post-link函數(shù)的時候,它的所有子級指令的post-link函數(shù)是已經(jīng)執(zhí)行過的.
在大部分的情況下,它都可以做的更好,因此通常我們都會使用它來編寫指令代碼.
然而,ng為我們提供了一個附加的hook機制,那就是pre-link函數(shù),它能夠保證在執(zhí)行所有子指令的post-link函數(shù)之前.運行一些別的代碼.
這句話是值得反復推敲的
pre-link函數(shù)能夠保證在element實例上以及它的所有子指令的post-link運行之前執(zhí)行.
所以它使的post-link函數(shù)反向執(zhí)行是相當有意義的,它自己是原始的順序執(zhí)行pre-link函數(shù)
這也意為著pre-link函數(shù)運行在它所有子指令的pre-link函數(shù)之前,所以完整的理由就是:
一個元素的pre-link函數(shù)能夠保證是運行在它所有的子指令的post-link與pre-link運行之前執(zhí)行的.見下圖:
回顧
如果我們回頭看看上面原始的輸出,就能清楚的認出到底發(fā)生了什么:
// 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
概要
回顧上面的分析我們可以描述一下這些函數(shù)的區(qū)別以及使用情況:
Compile 函數(shù)
使用compile函數(shù)可以改變原始的dom(template element),在ng創(chuàng)建原始dom實例以及創(chuàng)建scope實例之前.
可以應用于當需要生成多個element實例,只有一個template element的情況,ng-repeat就是一個最好的例子,它就在是compile函數(shù)階段改變原始的dom生成多個原始dom節(jié)點,然后每個又生成element實例.因為compile只會運行一次,所以當你需要生成多個element實例的時候是可以提高性能的.
template element以及相關的屬性是做為參數(shù)傳遞給compile函數(shù)的,不過這時候scope是不能用的:
下面是函數(shù)樣子:
/**
* Compile function
*
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(tElem, tAttrs){
// ...
};
Pre-link 函數(shù)
使用pre-link函數(shù)可以運行一些業(yè)務代碼在ng執(zhí)行完compile函數(shù)之后,但是在它所有子指令的post-link函數(shù)將要執(zhí)行之前.
scope對象以及element實例將會做為參數(shù)傳遞給pre-link函數(shù):
下面是函數(shù)樣子:
/**
* 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 函數(shù)
使用post-link函數(shù)來執(zhí)行業(yè)務邏輯,在這個階段,它已經(jīng)知道它所有的子指令已經(jīng)編譯完成并且pre-link以及post-link函數(shù)已經(jīng)執(zhí)行完成.
這就是被認為是最安全以及默認的編寫業(yè)務邏輯代碼的原因.
scope實例以及element實例做為參數(shù)傳遞給post-link函數(shù):
下面是函數(shù)樣子:
/**
* 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){
// ...
};
總結(jié)
現(xiàn)在你應該對compile,pre-link,post-link這此函數(shù)之間的區(qū)別有了清晰的認識了吧.
如果還沒有的話,并且你是一個認真的ng開發(fā)者,那么我強烈建議你重新把這篇文章讀一讀直到你了解為止
理解這些概念非常重要,能夠幫助你理解ng原生指令的工作原理,也能幫你優(yōu)化你自己的自定義指令.
如果還有問題的話,歡迎大家在下面評論里加上你的問題
以后還會接著分析關于指令里的其它兩個問題:
1.指令使用transclusion屬性是怎么工作的?
2.指令的controller函數(shù)是怎么關聯(lián)的
最后,如果發(fā)現(xiàn)本文哪里有不對的,請及時給我發(fā)評論
謝謝!
相關文章
淺談angular2的http請求返回結(jié)果的subcribe注意事項
下面小編就為大家?guī)硪黄獪\談angular2的http請求返回結(jié)果的subcribe注意事項。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03一篇文章快速了解Angular和Ionic生命周期和鉤子函數(shù)
Ionic以AngularJS和ApacheCordova為基礎,使用Node.js進行模塊管理,使用Html5、Css(SASS)和Javascript技術(shù)進行APP開發(fā),這篇文章主要給大家介紹了如何通過一篇文章快速了解Angular和Ionic生命周期和鉤子函數(shù)的相關資料,需要的朋友可以參考下2021-07-07自定義Angular指令與jQuery實現(xiàn)的Bootstrap風格數(shù)據(jù)雙向綁定的單選與多選下拉框
這篇文章主要介紹了自定義Angular指令與jQuery實現(xiàn)的Bootstrap風格數(shù)據(jù)雙向綁定的單選與多選下拉框 的相關資料,需要的朋友可以參考下2015-12-12