1 前言
前端技術的發展是如此之快,各種優秀技術、優秀框架的出現簡直讓人目不暇接,緊跟時代潮流,學習掌握新知識自然是不敢怠慢。
AngularJS是google在維護,其在國外已經十分火熱,可是國內的使用情況卻有不小的差距,參考文獻/網路文章也很匱乏。這裡便將我學習AngularJS寫成文件,一方面作為自己學習路程上的記錄,另一方面也給有興趣的同學一些參考。
首先我自己也是一名學習者,會以學習者的角度來整理我的行文思路,這裡可能只是些探索,有理解或是技術上的錯誤還請大家指出;其次我特別喜歡編寫小例子來把一件事情說明白,故在文中會盡可能多的用示例加程式碼講解,我相信這會是一種比較好的方式;最後,我深知AngularJS的使用方式跟jquery的使用方式有很大不同,在大家都有jquery、ext經驗的條件下對於angular的學習會困難重重,不過我更相信在大家的堅持下,能夠快速的學好AngularJS,至少咱也能深入瞭解到AngularJS的基本思想,對咱們以後自己的外掛開發、專案開發都會有很大的啟示。
2 AngularJS概述
2.1 AngularJS是什麼?
AngularJs(後面就簡稱ng了)是一個用於設計動態web應用的結構框架。首先,它是一個框架,不是類庫,是像EXT一樣提供一整套方案用於設計web應用。它不僅僅是一個javascript框架,因為它的核心其實是對HTML標籤的增強。
何為HTML標籤增強?其實就是使你能夠用標籤完成一部分頁面邏輯,具體方式就是通過自定義標籤、自定義屬性等,這些HTML原生沒有的標籤/屬性在ng中有一個名字:指令(directive)。後面會詳細介紹。那麼,什麼又是動態web應用呢?與傳統web系統相區別,web應用能為使用者提供豐富的操作,能夠隨使用者操作不斷更新檢視而不進行url跳轉。ng官方也宣告它更適用於開發CRUD應用,即資料操作比較多的應用,而非是遊戲或影象處理類應用。
為了實現這些,ng引入了一些非常棒的特性,包括模板機制、資料繫結、模組、指令、依賴注入、路由。通過資料與模板的繫結,能夠讓我們擺脫繁瑣的DOM操作,而將注意力集中在業務邏輯上。
另外一個疑問,ng是MVC框架嗎?還是MVVM框架?官網有提到ng的設計採用了MVC的基本思想,而又不完全是MVC,因為在書寫程式碼時我們確實是在用ng-controller這個指令(起碼從名字上看,是MVC吧),但這個controller處理的業務基本上都是與view進行互動,這麼看來又很接近MVVM。讓我們把目光移到官網那個非醒目的title上:“AngularJS — Superheroic JavaScript MVW Framework”。
2.2 AngularJS簡單介紹
AngularJS 重新定義了前端應用的開發方式。面對HTML和JavaScript之間的界線,它
非但不畏縮不前,反而正面出擊,提出了有效的解決方案。
很多前端應用的開發框架,比如Backbone、EmberJS等,都要求開發者繼承此框架特有的一些JavaScript物件。這種方式有其長處,但它不必要地汙染了開發者自己程式碼的物件空間,還要求開發者去了解記憶體裡那些抽象物件。儘管如此我們還是接受了這種方式,因為網路最初的設計無法提供 我們今天所需的互動性,於是我們需要框架,來幫我們填補JavaScript和HTML之間的鴻溝。而且有了它,你不用再“直接”操控DOM,只要給你的DOM註上metadata(即AngularJS裡的directive們),然後讓AngularJS來幫你操縱DOM。同時,AngularJS不依賴(也不妨礙)任何其他的框架。你甚至可以基於其它的框架來開發AngularJS應用。
API地址:http://docs.angularjs.org/api/;
AngularJS在github上的中文粗譯版地址:https://github.com/basestyle/angularjs-cn。
2.3 什麼時候該用AngularJS
AngularJS是一個 MV* 框架,最適於開發客戶端的單頁面應用。它不是個功能庫,而是用來開發動態網頁的框架。它專注於擴充套件HTML的功能,提供動態資料繫結(data binding),而且它能跟其它框架(如jQuery)合作融洽。
如果你要開發的是單頁應用,AngularJS就是你的上上之選。Gmail、Google Docs、Twitter和Facebook這樣的應用,都很能發揮AngularJS的長處。但是像遊戲開發之類對DOM進行大量操縱、又或者單純需要 極高執行速度的應用,就不是AngularJS的用武之地了。
3 AugularJS特性
AngularJS是一個新出現的強大客戶端技術,提供給大家的一種開發強大應用的方式。這種方式利用並且擴充套件HTML,CSS和javascript,並且彌補了它們的一些非常明顯的不足。本應該使用HTML來實現而現在由它開發的動態一些內容。
AngularJS有五個最重要的功能和特性:
3.1 特性一:雙向的資料繫結
資料繫結可能是AngularJS最酷最實用的特性。它能夠幫助你避免書寫大量的初始程式碼從而節約開發時間。一個典型的web應用可能包含了80%的程式碼用來處理,查詢和監聽DOM。資料繫結是的程式碼更少,你可以專注於你的應用。
我們想象一下Model是你的應用中的簡單事實。你的Model是你用來讀取或者更新的部分。資料繫結指令提供了你的Model投射到view的方法。這些投射可以無縫的,毫不影響的應用到web應用中。
傳統來說,當model變化了。 開發人員需要手動處理DOM元素並且將屬性反映到這些變化中。這個一個雙向的過程。一方面,model變化驅動了DOM中元素變化,另一方面,DOM元素的變化也會影響到Model。這個在使用者互動中更加複雜,因為開發人員需要處理和解析
這些互動,然後融合到一個model中,並且更新View。這是一個手動的複雜過程,當一個應用非常龐大的時候,將會是一件非常費勁的事情。
這裡肯定有更好的解決方案!那就是AngularJS的雙向資料繫結,能夠同步DOM和Model等等。
這裡有一個非常簡單的例子,用來演示一個input輸入框和<h1>元素的雙向繫結(例01):
說明:實際效果請大家看AngularJS/demo/index.html
3.2 特性二:模板
在AngularJS中,一個模板就是一個HTML檔案。但是HTML的內容擴充套件了,包含了很多幫助你對映model到view的內容。
HTML模板將會被瀏覽器解析到DOM中。DOM然後成為AngularJS編譯器的輸入。AngularJS將會遍歷DOM模板來生成一些指導,即,directive(指令)。所有的指令都負責針對view來設定資料繫結。
我們要理解AuguarJS並不把模板當做String來操作。輸入AngularJS的是DOM而非string。資料繫結是DOM變化,不是字串的連線或者innerHTML變化。使用DOM作為輸入,而不是字串,是AngularJS區別於其它的框架的最大原因。使用DOM允許你擴充套件指令詞彙並且可以建立你自己的指令,甚至開發可重用的元件。
最大的好處是為設計師和開發者建立了一個緊密的工作流。設計師可以像往常一樣開發標籤,然後開發者拿過來新增上功能,通過資料繫結將會使得這個過程非常簡單。
這裡有一個例子,我們使用ng-repeat指令來迴圈圖片陣列並且加入img模板,如下:
function AlbumCtrl($scope) {
scope.images = [
{"image":"img/image_01.png", "description":"Image 01 description"},
{"image":"img/image_02.png", "description":"Image 02 description"},
{"image":"img/image_03.png", "description":"Image 03 description"},
{"image":"img/image_04.png", "description":"Image 04 description"},
{"image":"img/image_05.png", "description":"Image 05 description"}
];
}
<div ng-controller="AlbumCtrl">
<ul>
<li ng-repeat="image in images">
<img ng-src="http://m.cnblogs.com/142260/{{image.thumbnail}}" rel="nofollow"/>
</li>
</ul>
</div>
這裡還有一件事值得提一句,AngularJS並不強制你學習一個新的語法或者從你的應用中提出你的模板。
3.3 特性三:MVC
針對客戶端應用開發AngularJS吸收了傳統的MVC基本原則。MVC或者Model-View-Controll設計模式針對不同的人可能意味不同的東西。AngularJS並不執行傳統意義上的MVC,更接近於MVVM(Moodel-View-ViewModel)。
Model
model是應用中的簡單資料。一般是簡單的javascript物件。這裡沒有必要繼承框架的classes,使用proxy物件封裝或者使用特別的setter/getter方法來訪問。事實上我們處理vanilla javascript的方法就是一個非常好的特性,這種方法使得我們更少使用應用的原型。
ViewModel
viewmodel是一個用來提供特別資料和方法從而維護指定view的物件。
viewmodel是$scope的物件,只存在於AnguarJS的應用中。$scope只是一個簡單的js物件,這個物件使用簡單的API來偵測和廣播狀態變化。
Controller
controller負責設定初始狀態和引數化$scope方法用以控制行為。需要指出的controller並不儲存狀態也不和遠端服務互動。
View
view是AngularJS解析後渲染和繫結後生成的HTML 。這個部分幫助你建立web應用的架構。$scope擁有一個針對資料的參考,controller定義行為,view處理佈局和互動。
3.4 特性四:服務和依賴注入
AngularJS服務其作用就是對外提供某個特定的功能。
AngularJS擁有內建的依賴注入(DI)子系統,可以幫助開發人員更容易的開發,理解和測試應用。
DI允許你請求你的依賴,而不是自己找尋它們。比如,我們需要一個東西,DI負責找建立並且提供給我們。
為了而得到核心的AngularJS服務,只需要新增一個簡單服務作為引數,AngularJS會偵測並且提供給你:
function EditCtrl($scope, $location, $routeParams) {
// Something clever here...
}
你也可以定義自己的服務並且讓它們注入:
angular.module('MyServiceModule', []).
factory('notify', ['$window', function (win) {
return function (msg) {
win.alert(msg);
};
}]);
function myController(scope, notifyService) {
scope.callNotify = function (msg) {
notifyService(msg);
};
}
myController.$inject = ['$scope', 'notify'];
3.5 特性五:指令(Directives)
指令是我個人最喜歡的特性。你是不是也希望瀏覽器可以做點兒有意思的事情?那麼AngularJS可以做到。
指令可以用來建立自定義的標籤。它們可以用來裝飾元素或者操作DOM屬性。可以作為標籤、屬性、註釋和類名使用。
這裡是一個例子,它監聽一個事件並且針對的更新它的$scope ,如下:
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
然後,你可以使用這個自定義的directive來使用:
<my-component ng-model="message"></my-component>
使用一系列的元件來建立你自己的應用將會讓你更方便的新增,刪除和更新功能。
4 功能介紹
4.1資料繫結
AngularJS的雙向資料繫結,意味著你可以在Mode(JS)中改變資料,而這些變動立刻就會自動出現在View上,反之亦然。即:一方面可以做到model變化驅動了DOM中元素變化,另一方面也可以做到DOM元素的變化也會影響到Model。
在我們使用jQuery的時候,程式碼中會大量充斥類似這樣的語句:var val = $(‘#id’).val(); $(‘#id’).html(str);等等,即頻繁的DOM操作(讀取和寫入),其實我們的最終目的並不是要操作DOM,而是要實現業務邏輯。ng的繫結將讓你擺脫DOM操作,只要模板與資料通過宣告進行了繫結,兩者將隨時保持同步,最新的資料會實時顯示在頁面中,頁面中使用者修改的資料也會實時被記錄在資料模型中。
從View到Controller再到View的資料互動(例01):
<html ng-app="demoApp">
……
<input type="text" ng-model="user.name" placeholder="請輸入名稱"/>
Hello, {{ user.name }}!
……
關鍵: ng-app 、 ng-model 和 { {user.name } }
首先: <html>元素的ng-app屬性。標識這個DOM裡面的內容將啟用AngularJS應用。
其次:告訴AngularJS,對頁面上的“user.name” 這個Model進行雙向資料繫結。
第三:告訴AngularJS,在“{{ user.name}}”這個指令模版上顯示“user.name”這個Model的資料。
從Server到Controller再到View的資料互動(例02):
<html ng-app="demoApp">
……
<div ng-controller="demoController">
<input type="text" ng-model="user.name" disabled="disabled"/>
<a href="javascript:void(0);" target="_blank" rel="nofollow">獲取名字</a>
……
demoApp.controller("demoController", function($http, $scope){
$scope. getAjaxUser = function(){
// $http.get({url:"../xxx.action"}).success(function(data){
// $scope.user= data;
// });
$scope.user = {"name":"從JOSN中獲取的名稱","age":22};
};
});
改變$scope中的user,View也會自動更新。
4.2 scopes、module、controller
4.2.1 scopes
$scope是一個把view(一個DOM元素)連結到controller上的物件。在我們的MVC結構裡,這個 $scope 將成為model,它提供一個繫結到DOM元素(以及其子元素)上的excecution context。
儘管聽起來有點複雜,但 $scope 實際上就是一個JavaScript物件,controller和view都可以訪問它,所以我們可以利用它在兩者間傳遞資訊。在這個 $scope 物件裡,我們既儲存資料,又儲存將要執行在view上的函式。
每一個Angular應用都會有一個 $rootScope。這個 $rootScope 是最頂級的scope,它對應著含有 ng-app 指令屬性的那個DOM元素。
app.run(function($rootScope) { $rootScope.name = "張三"; });
如果頁面上沒有明確設定 $scope ,Angular 就會把資料和函式都繫結到這裡, 第一部分中的例子就是靠這一點成功執行的。
這樣,我們就可以在view的任何地方訪問這個name屬性,使用模版表示式{{}},像這樣:
{{ name }}
4.2.2 module
首先需要明確一下模板的概念。在我還不知道有模板這個東西的時候,曾經用js拼接出很長的HTML字串,然後append到頁面中,這種方式想想真是又土又笨。後來又看到可以把HTML程式碼包裹在一個<script>標籤中當作模板,然後按需要取來使用。
在ng中,模板十分簡單,它就是我們頁面上的HTML程式碼,不需要附加任何額外的東西。在模板中可以使用各種指令來增強它的功能,這些指令可以讓你把模板和資料巧妙的繫結起來。
在<html>標籤上多了一個屬性ng-app=”MyApp”,它的作用就是用來指定ng的作用域是在<html>標籤以內部分。在js中,我們呼叫angular物件的module方法來宣告一個模組,模組的名字和ng-app的值對應。這樣宣告一下就可以讓ng執行起來了。
示例:
<html ng-app="demoApp">
var demoApp = angular.module('demoApp', []);
4.2.3 ng-controller
要明確建立一個$scope 物件,我們就要給DOM元素安上一個controller物件,使用的是ng-controller 指令屬性:
<div ng-controller="MyController"> {{ person.name }} </div>
ng-controller指令給所在的DOM元素建立了一個新的$scope 物件,並將這個$scope 物件包含進外層DOM元素的$scope 物件裡。在上面的例子裡,這個外層DOM元素的$scope 物件,就是$rootScope 物件。這個scope鏈是這樣的:
所有scope都遵循原型繼承(prototypal inheritance),這意味著它們都能訪問父scope們。對任何屬性和方法,如果AngularJS在當前scope上找不到,就會到父 scope上去找,如果在父scope上也沒找到,就會繼續向上回溯,一直到$rootScope 上。即如果controller是多層巢狀的,就會從最裡面一直往外找,這個scope鏈是這樣的:
唯一的例外:有些指令屬性可以選擇性地建立一個獨立的scope,讓這個scope不繼承它的父scope們,這個會在指令詳解中說明。
4.3 ajax
$http 服務是AngularJS的核心服務之一,它幫助我們通過XMLHttpRequest物件或JSONP與遠端HTTP服務進行交流。
$http 服務是這樣一個函式:它接受一個設定物件,其中指定了如何建立HTTP請求;它將返回一個承諾(*參考JavaScript非同步程式設計的promise模式),其中提供兩個方法: success方法和error方法。
demoApp.controller("demoController", function($http, $scope){
$scope. getAjaxUser = function(){
$http.get({url:"../xxx.action"}).success(function(data){
alert(data);
}).error(function(){
Alert(“出錯了!”);
});
};
});
AngularJS的AJAX與jquery等框架的AJAX基本一致,這裡就不多說了。
4.4表示式
ng中的表示式與javascript表示式類似但是不可以劃等號,它是ng自己定義的一套模式。表示式可以作為指令的值,如ng-modle=”people.name”、ng-click=”showMe()”,看起來是如此像字串,故而也叫字串表示式。也可以在標記中使用表示式,如{{1+2}},或者與過濾器一起使用{{1+2 | currency}}。在框架內部,字串不會簡單的使用eval()來執行,而是有一個專門的$parse服務來處理。在ng表示式中不可以使用迴圈語句、判斷語句,事實上在模板中使用複雜的表示式也是一個不推薦的做法,這樣檢視與邏輯就混雜在一起了
我們在使用其他模板庫時,一般都會有模板的迴圈輸出、分支輸出、邏輯判斷等類似的控制。
要想理解指令屬性的運作,我們必須先理解表示式。在之前的例子裡我們已經見過表示式,例如 {{ user.name }}。
請檢視例03、例04、例05。
{{ 8 + 1 }} 9
{{ person }} {"name":"Ari Lerner"}
{{ 10 * 3.3 | currency }} $33.00
表示式粗略來看有點像 eval(javascript) 的結果。它們會經過Angular.js的處理,從而擁有以下重要而獨特的性質:
l 所有表示式都在scope這個context裡被執行,因此可以使用所有本地 $scope 中的變數。
l 如果一個表示式的執行導致型別錯誤或引用錯誤,這些錯誤將不會被丟擲。
l 表示式裡不允許任何控制函式流程的功能(如if/else等條件語句)
l 表示式可接受一個或多個串聯起來的過濾器。
4.5過濾器
過濾器(filter)正如其名,作用就是接收一個輸入,通過某個規則進行處理,然後返回處理後的結果。主要用在資料的格式化上,例如獲取一個數組中的子集,對陣列中的元素進行排序等。過濾器通常是伴隨標記來使用的,將你model中的資料格式化為需要的格式。表單的控制功能主要涉及到資料驗證以及表單控制元件的增強。ng內建了一些過濾器,它們是:
currency(貨幣)、date(日期)、filter(子串匹配)、json(格式化json物件)、limitTo(限制個數)、lowercase(小寫)、uppercase(大寫)、number(數字)、orderBy(排序)。
4.5.1過濾器使用方式
總共九種。除此之外還可以自定義過濾器,這個就強大了,可以滿足任何要求的資料處理。Filter還是很簡單的,需要明白的是內建的filter如何使用,以及自己如何定義一個filter。
filter的兩種使用方法:
1. 在模板中使用filter
我們可以直接在{{}}中使用filter,跟在表示式後面用 | 分割,語法如下:
{{ expression | filter }}
也可以多個filter連用,上一個filter的輸出將作為下一個filter的輸入:
{{ expression | filter1 | filter2 | ... }}
filter可以接收引數,引數用 : 進行分割,如下:
{{ expression | filter:argument1:argument2:... }}
除了對{{}}中的資料進行格式化,我們還可以在指令中使用filter,例如先對陣列array進行過濾處理,然後再迴圈輸出:
<span ng-repeat="a in array | filter ">
2. 在controller和service中使用filter
我們的js程式碼中也可以使用過濾器,方式就是我們熟悉的依賴注入,例如我要在controller中使用currency過濾器,只需將它注入到該controller中即可,程式碼如下:
app.controller('testC',function($scope,currencyFilter){
$scope.num = currencyFilter(123534);
}
在模板中使用{{num}}就可以直接輸出$123,534.00了!在服務中使用filter也是同樣的道理。
如果你要在controller中使用多個filter,並不需要一個一個注入嗎,ng提供了一個$filter服務可以來呼叫所需的filter,你只需注入一個$filter就夠了,使用方法如下:
app.controller('testC',function($scope,$filter){
$scope.num = $filter('currency')(123534);
$scope.date = $filter('date')(new Date());
}
可以達到同樣的效果。好處是你可以方便使用不同的filter了。
4.5.2 ng的內建過濾器
ng內建了九種過濾器,使用方法都非常簡單,看文件即懂。不過為了以後不去翻它的文件,我在這裡還是做一個詳細的記錄。
currency(貨幣)、date(日期)、filter(子串匹配)、json(格式化json物件)、limitTo(限制個數)、lowercase(小寫)、uppercase(大寫)、number(數字)、orderBy(排序)
1. currency (貨幣處理)
使用currency可以將數字格式化為貨幣,預設是美元符號,你可以自己傳入所需的符號,例如我傳入人民幣:
{{num | currency : '¥'}}
2. date (日期格式化)
原生的js對日期的格式化能力有限,ng提供的date過濾器基本可以滿足一般的格式化要求。用法如下:
{{date | date : 'yyyy-MM-dd hh:mm:ss EEEE'}}
引數用來指定所要的格式,y M d h m s E 分別表示 年 月 日 時 分 秒 星期,你可以自由組合它們。也可以使用不同的個數來限制格式化的位數。另外引數也可以使用特定的描述性字串,例如“shortTime”將會把時間格式為12:05 pm這樣的。ng提供了八種描述性的字串,個人覺得這些有點多餘,我完全可以根據自己的意願組合出想要的格式,不願意去記這麼多單詞~
3. filter(匹配子串)
這個名叫filter的filter。用來處理一個數組,然後可以過濾出含有某個子串的元素,作為一個子陣列來返回。可以是字串陣列,也可以是物件陣列。如果是物件陣列,可以匹配屬性的值。它接收一個引數,用來定義子串的匹配規則。下面舉個例子說明一下引數的用法,我用現在特別火的幾個孩子定義了一個數組:
$scope.childrenArray = [
{name:'kimi',age:3},
{name:'cindy',age:4},
{name:'anglar',age:4},
{name:'shitou',age:6},
{name:'tiantian',age:5}
];
$scope.func = function(e){return e.age>4;}{{ childrenArray | filter : 'a' }} //匹配屬性值中含有a的
{{ childrenArray | filter : 4 }} //匹配屬性值中含有4的
{{ childrenArray | filter : {name : 'i'} }} //引數是物件,匹配name屬性中含有i的
{{childrenArray | filter : func }} //引數是函式,指定返回age>4的
4. json(格式化json物件)
json過濾器可以把一個js物件格式化為json字串,沒有引數。這東西有什麼用呢,我一般也不會在頁面上輸出一個json串啊,官網說它可以用來進行除錯,嗯,是個不錯的選擇。或者,也可以用在js中使用,作用就和我們熟悉的JSON.stringify()一樣。用法超級簡單:
{{ jsonTest | json}}
5. limitTo(限制陣列長度或字串長度)
limitTo過濾器用來擷取陣列或字串,接收一個引數用來指定擷取的長度,如果引數是負值,則從陣列尾部開始擷取。個人覺得這個filter有點雞肋,首先只能從陣列或字串的開頭/尾部進行擷取,其次,js原生的函式就可以代替它了,看看怎麼用吧:
{{ childrenArray | limitTo : 2 }} //將會顯示陣列中的前兩項
6. lowercase(小寫)
把資料轉化為全部小寫。太簡單了,不多解釋。同樣是很雞肋的一個filter,沒有引數,只能把整個字串變為小寫,不能指定字母。怎麼用我都懶得寫了。
7. uppercase(大寫)
同上。
8. number(格式化數字)
number過濾器可以為一個數字加上千位分割,像這樣,123,456,789。同時接收一個引數,可以指定float型別保留幾位小數:
{{ num | number : 2 }}
9. orderBy(排序)
orderBy過濾器可以將一個數組中的元素進行排序,接收一個引數來指定排序規則,引數可以是一個字串,表示以該屬性名稱進行排序。可以是一個函式,定義排序屬性。還可以是一個數組,表示依次按陣列中的屬性值進行排序(若按第一項比較的值相等,再按第二項比較),還是拿上面的孩子陣列舉例:
<div>{{ childrenArray | orderBy : 'age' }}</div> //按age屬性值進行排序,若是-age,則倒序
<div>{{ childrenArray | orderBy : orderFunc }}</div> //按照函式的返回值進行排序
<div>{{ childrenArray | orderBy : ['age','name'] }}</div> //如果age相同,按照name進行排序 內建的過濾器介紹完了,寫的我都快睡著了。。。正如你所看到的,ng內建的過濾器也並不是萬能的,事實上好多都比較雞肋。更個性化的需求就需要我們來定義自己的過濾器了,下面來看看如何自定義過濾器。
4.5.3自定義過濾器及示例
filter的自定義方式也很簡單,使用module的filter方法,返回一個函式,該函式接收
輸入值,並返回處理後的結果。話不多說,我們來寫一個看看。比如我需要一個過濾器,它可以返回一個數組中下標為奇數的元素,程式碼如下:
app.filter('odditems',function(){
return function(inputArray){
var array = [];
for(var i=0;i<inputArray.length;i++){
if(i%2!==0){
array.push(inputArray[i]);
}
}
return array;
}
});
格式就是這樣,你的處理邏輯就寫在內部的那個閉包函式中。你也可以讓自己的過濾器接收引數,引數就定義在return的那個函式中,作為第二個引數,或者更多個引數也可以。
自定義過濾器例項(例04):
/* View html */
First name:<input ng-model="user.firstName"/><br/>
Last name:<input ng-model="user.lastName"/> <br/>
First name:{{user.firstName}} Last name:{{user.lastName}} <br/>
Fullname:{{user | flFullname}}<br/>
Fullname:{{user | flFullname:"-"}}<br/>
Fullname:{{user | flFullname:"•" | uppercase }}
/* Controller js */
demoApp.filter("flFullname", function() {
return function(user, sep) {
sep = sep || " ";
user = user || {};
fullName = "";
if(user.firstName){fullName += user.firstName;}
if(user.lastName){fullName = fullName + sep + user.lastName;}
if(fullName && fullName.length>0){return fullName;
}else{return "";}
};
});
4.6指令(directive)
通過使用模板,我們可以把model和controller中的資料組裝起來呈現給瀏覽器,還可以通過資料繫結,實時更新檢視,讓我們的頁面變成動態的。
模板中可以使用的東西包括以下四種:
1.指令(directive):ng提供的或者自定義的標籤和屬性,用來增強HTML表現力;
2.標記(markup):即雙大括號{{}},可將資料單向繫結到HTML中;
3.過濾器(filter):用來格式化輸出資料;
4.表單控制:用來增強表單的驗證功能。
其中,指令無疑是使用量最大的,ng內建了很多指令用來控制模板,如ng-repeat,ng-class,也有很多指令來幫你完成業務邏輯,如ng-controller,ng-model。
指令的幾種使用方式如下:
l 作為標籤:<my-dir></my-dir>
l 作為屬性:<span my-dir="exp"></span>
l 作為註釋:<!-- directive: my-dir exp -->
l 作為類名:<span class="my-dir: exp;"></span>
其實常用的就是作為標籤和屬性。
4.6.1樣式相關的指令
既然模板就是普通的HTML,那我首要關心的就是樣式的控制,元素的定位、字型、背景色等等如何可以靈活控制。下面來看看常用的樣式控制指令。
1. ng-class
ng-class用來給元素繫結類名,其表示式的返回值可以是以下三種:
l 類名字串,可以用空格分割多個類名,如’redtext boldtext’;
l 類名陣列,陣列中的每一項都會層疊起來生效;
l 一個名值對應的map,其鍵值為類名,值為boolean型別,當值為true時,該類會被加在元素上。
下面來看一個使用map的例子:
ng-class測試
紅色 加粗 刪除線
map:{redtext:{{red}}, boldtext:{{bold}}, striketext:{{strike}}}
如果你想拼接一個類名出來,可以使用插值表示式,如:
<div class=”{{style}}text”>字型樣式測試</div>
然後在controller中指定style的值:
$scope.style = ‘red’;
注意我用了class而不是ng-class,這是不可以對換的,官方的文件也未做說明,姑且認為這是ng的語法規則吧。
與ng-class相近的,ng還提供了ng-class-odd、ng-class-even兩個指令,用來配合ng-repeat分別在奇數列和偶數列使用對應的類。這個用來在表格中實現隔行換色再方便不過了。
2. ng-style
ng-style用來繫結元素的css樣式,其表示式的返回值為一個js物件,鍵為css樣式名,值為該樣式對應的合法取值。用法比較簡單:
<div ng-style="{color:'red'}">ng-style測試</div>
<div ng-style="style">ng-style測試</div>
$scope.style = {color:'red'};
3. ng-show,ng-hide
對於比較常用的元素顯隱控制,ng也做了封裝,ng-show和ng-hide的值為boolean型別的表示式,當值為true時,對應的show或hide生效。框架會用display:block和display:none來控制元素的顯隱。
4.6.2表單控制元件功能相關指令
對於常用的表單控制元件功能,ng也做了封裝,方便靈活控制。
ng-checked控制radio和checkbox的選中狀態
ng-selected控制下拉框的選中狀態
ng-disabled控制失效狀態
ng-multiple控制多選
ng-readonly控制只讀狀態
以上指令的取值均為boolean型別,當值為true時相關狀態生效,道理比較簡單就不多做解釋。注意: 上面的這些只是單向繫結,即只是從資料到模板,不能反作用於資料。要雙向繫結,還是要使用 ng-model 。
4.6.3事件繫結相關指令
事件繫結是javascrpt中比較重要的一部分內容,ng對此也做了詳細的封裝,正如我們之前使用過的ng-click一樣,事件的指令如下:
ng-click
ng-change
ng-dblclick
ng-mousedown
ng-mouseenter
ng-mouseleave
ng-mousemove
ng-mouseover
ng-mouseup
ng-submit
事件繫結指令的取值為函式,並且需要加上括號,例如:
<select ng-change=”change($event)”></select>
然後在controller中定義如下:
$scope.change = function($event){
alert($event.target);
//……………………
}
在模板中可以用變數$event將事件物件傳遞到controller中。
對於ng的這種設計,一些人有所質疑,檢視與事件繫結混在一起到底好不好?我們不是要講究檢視與邏輯分離嗎?如此一來,把事件的繫結又變回了內聯的,豈不是歷史的倒退。我也一樣對此表示不解,因為不寫onclick已經很多年。。。但既然已經存在了,我們不妨往合理的方向上想一想,或許ng的設計者壓根就不想讓模板成為單純的檢視層,本來就是想增強HTML,讓它有一點業務能力。這麼想的話似乎也能想通,好吧,先欺騙一下自己吧~
4.6.4特殊的ng-src和ng-href
在說明這兩個指令的特殊之前,需要先了解一下ng的啟動及執行過程,如下圖:
1) 瀏覽器載入靜態HTML檔案並解析為DOM;
2) 瀏覽器載入angular.js檔案;
3) angular監聽DOMContentLoaded 事件,監聽到時開始啟動;
4) angular尋找ng-app指令,確定作用範圍;
5) 找到app中定義的Module使用$injector服務進行依賴注入;
6) 根據$injector服務建立$compile服務用於編譯;
7) $compile服務編譯DOM中的指令、過濾器等;
8) 使用ng-init指令,將作用域中的變數進行替換;
9) 最後生成了我們在最終檢視。
可以看到,ng框架是在DOMcontent載入完畢後才開始發揮作用。假如我們模板中有一張圖片如下:
<img src="http://m.cnblogs.com/142260/”{{imgUrl}}” />
那麼在頁面開始載入到ng編譯完成之前,頁面上會一直顯示一張錯誤的圖片,因為路徑{{imgUrl}}還未被替換。
為了避免這種情況,我們使用ng-src指令,這樣在路徑被正確得到之前就不會顯示找不到圖片。同理,<a>標籤的href屬性也需要換成ng-href,這樣頁面上就不會先出現一個地址錯誤的連結。
順著這個思路再多想一點,我們在模板中使用{{}}顯示資料時,在ng編譯完成之前頁面上豈不是會顯示出大括號及裡面的表示式?確實是這樣。為了避免這個,ng中有一個與{{}}等同的指令:ng-bind,同樣用於單向繫結,在頁面剛載入的時候就不會顯示出對使用者無用的資料了。儘管這樣你可能不但沒舒心反而更糾結了,{{}}那麼好用易理解,還不能用了不成?好訊息是我們依然可以使用。因為我編寫的是單頁面應用,頁面只會在載入index.html的時
候出這個問題,只需在index.html中的模板中換成ng-bind就行。其他的模板是我們動態載入的,就可以放心使用{{}}了。
4.6.5 自定義指令示例
下面我們來解析下指令的例子(例07)。
1.首先,我們定義一個名為userInfo的指令:
demoApp.directive('userInfo',function(){
return {
restrict : 'E',
templateUrl : 'userInfoTemplate.html',
replace : true,
transclude : true,
scope : {
mytitle : '=etitle'
},
link : function(scope,element,attrs){
scope.showText = false;
scope.toggleText = function(){
scope.showText = ! scope.showText;
}
}
};
})
Restrict為'E':用作標籤;replace為true:用模板替換當前標籤;transclude為true:將當前元素的內容轉移到模板中;scope 為 {mytitle : '=etitle'}:定義一個名為mytitle的MODEL,其值指向當前元素的etitle屬性;templateUrl為'userInfoTemplate.html':模板內容為ng-template定義ID為userInfoTemplate.html的內容;link:指定所包含的行為。其具體的說明及其他引數,請參考:6.2指令詳解。
2. userInfoTemplate.html模板為:
<script type="text/ng-template" id="userInfoTemplate.html">
<div class="mybox">
<div class="mytitle" style="cursor: pointer;" ng-click="toggleText()">
{ {mytitle} }
</div>
<div ng-transclude ng-show="showText">
</div>
</div>
</script>
將當前元素的內容新增到有ng-transclude屬性的這個DIV下,預設是隱藏的。
3.Controller資訊:
demoApp.controller("test7Controller", function($scope){
$scope.title = '個人簡介';
$scope.text = '大家好,我正在研究AngularJs,歡迎大家與我交流。';
$scope.updateInfo = function (){
$scope.title = '個人資訊';
$scope.text = '大家好,今天天氣真好!';
}
});
4.指令使用方式(View資訊)為:
<user-info etitle="title">{ {text} }</user-info>
Etitle指向Controller中的$scope.title。注意命名方式:指令名為userInfo,對應的標籤為user-info。
4.7服務(service)
4.7.1服務介紹
服務這個概念其實並不陌生,在其他語言中如java便有這樣的概念,其作用就是對外提供某個特定的功能,如訊息服務,檔案壓縮服務等,是一個獨立的模組。ng的服務是這樣定義的:
Angular services are singletons objects or functions that carry out specific tasks common to web apps.
它是一個單例物件或函式,對外提供特定的功能。
首先是一個單例,即無論這個服務被注入到任何地方,物件始終只有一個例項。
其次這與我們自己定義一個function然後在其他地方呼叫不同,因為服務被定義在一個模組中,所以其使用範圍是可以被我們管理的。ng的避免全域性變數汙染意識非常強。
ng提供了很多內建的服務,可以到API中檢視http://docs.angularjs.org/api/。知道了概念,我們來拉一個service出來溜溜,看看到底是個什麼用法。
我們在controller中直接宣告$location服務,這依靠ng的依賴注入機制。$location提供位址列相關的服務,我們在此只是簡單的獲取當前的地址。
服務的使用是如此簡單,我們可以把服務注入到controller、指令或者是其他服務中。
4.7.2自定義服務
如同指令一樣,系統內建的服務以$開頭,我們也可以自己定義一個服務。定義服務的方式有如下幾種:
l 使用系統內建的$provide服務;
l 使用Module的factory方法;
l 使用Module的service方法。
下面通過一個小例子來分別試驗一下。我們定義一個名為remoteData服務,它可以從遠端獲取資料,這也是我們在程式中經常使用的功能。不過我這裡沒有遠端伺服器,就寫死一點資料模擬一下。
//使用$provide來定義
var app = angular.module('MyApp', [], function($provide) {
$provide.factory('remoteData', function() {
var data = {name:'n',value:'v'};
return data;
});
});
//使用factory方法
app.factory('remoteData',function(){
var data = {name:'n',value:'v'};
return data;
});
//使用service方法
app.service('remoteData',function(){
this.name = 'n';
this.value = 'v';
});
Module的factory和$provide的factory方法是一模一樣的,從官網文件看它們其實就是一回事。至於Module內部是如何呼叫的,我此處並不打算深究,我只要知道怎麼用就好了。
再看Module的service方法,它沒有return任何東西,是因為service方法本身返回一個構造器,系統會自動使用new關鍵字來創建出一個物件。所以我們看到在構造器函式內可以使用this,這樣呼叫該服務的地方便可以直接通過remoteData.name來訪問資料了。
4.7.3管理服務的依賴關係
服務與服務中間可以有依賴關係,例如我們這裡定義一個名為validate的服務,它的作用是驗證資料是否合法,它需要依賴我們從遠端獲取資料的服務remoteData。程式碼如下:
在factory的引數中,我們可以直接傳入服務remoteData,ng的依賴注入機制便幫我們做好了其他工作。不過一定要保證這個引數的名稱與服務名稱一致,ng是根據名稱來識別的。若引數的名次與服務名稱不一致,你就必須顯示的宣告一下,方式如下:
app.factory('validate',['remoteData',function(remoteDataService){
return function(){
if(remoteDataService.name=='n'){
alert('驗證通過');
}
};
}]);
我們在controller中注入服務也是同樣的道理,使用的名稱需要與服務名稱一致才可以正確注入。否則,你必須使用$inject來手動指定注入的服務。比如:
function testC(scope,rd){
scope.getData = function(){
alert('name:'+rd.name+' value:'+rd.value);
}
}
testC.$inject = ['$scope','remoteData'];
在controller中注入服務,也可以在定義controller時使用陣列作為第二個引數,在此處
把服務注入進去,這樣在函式體中使用不一致的服務名稱也是可以的,不過要確保注入的順序是一致的,如:
app.controller('testC',['$scope','remoteData',function($scope,rd){
$scope.getData = function(){
alert('name:'+rd.name+' value:'+rd.value);
}
}]);
4.7.4 自定義服務示例
接下來讓我們看下例子(例08 自定義服務)程式碼,自定義userService服務:
demoApp.factory('userService', ['$http', function($http) {
var doGetUser = function(userId, path) {
//return $http({
//method: 'JSONP',
//url: path
//});
/*手動指定資料*/
var data = {userId:"woshishui",userName:"我是誰",userInfo:"我是誰!我是誰!"};;
if(userId=='zhangsan'){
data = {userId:"zhangsan",userName:"張三",userInfo:"我是張三,我為自己"};
}else if(userId=='lisi'){
data = {userId:"lisi",userName:"李四",userInfo:"我是李四,我為卿狂!"};
}
return data;
}
return {
/*userService對外暴露的函式,可有多個*/
getUser: function(userId) {
return doGetUser(userId, '../xxx/xxx.action');
}
};
}]);
我們建立了一個只有一個方法的userService,getUser為這個服務從後臺獲取使用者資訊的函式,並且對外暴露。當然,由於這是一個靜態的例子,無法訪問後臺,那麼我們便制定其返回的資料。
然後我們把這個服務新增到我們的controller中。我們建立一個controller並載入(或者注入)userService作為執行時依賴,我們把service的名字作為引數傳遞給controller 函式:
demoApp.controller("test8Controller", function($scope,userService){
/*文章資訊*/
$scope.articles = [{
title : "愛飛像風",
userId : "zhangsan",
userName : "張三"
},{
title : "無法停止的雨",
userId : "lisi",
userName : "李四"
}];
$scope.showUserInfo = false;//顯示作者詳細資訊開關
$scope.currentUser = {}; //當前選中的作者
$scope.getUserInfo = function(userId){
$scope.currentUser = userService.getUser(userId);
//呼叫 userService的getUser函式
$scope.showUserInfo = true;
setTimeout(function(){//定時器:隱藏作者詳細資訊
$scope.showUserInfo = false;
},3000);
}
});
我們的userService注入到我們的test8Controller後,我們就可以像使用其他服務(我們前面提到的$http服務)一樣的使用userService了。
相關的HTML程式碼如下:
/* View HTML*/
<tr ng-repeat="article_ in articles">
<td>
{{article_.title}}
</td>
<td>
<a href="javascript:void(0);" target="_blank" rel="nofollow">
</td>
</tr>
......
<div ng-show="showUserInfo">
使用者ID:{{currentUser.userId}}<br/>
使用者名稱:{{currentUser.userName}}<br/>
使用者簡介:{{currentUser.userInfo}}<br/>
</div>
4.8依賴注入DI
通過依賴注入,ng想要推崇一種宣告式的開發方式,即當我們需要使用某一模組或服務時,不需要關心此模組內部如何實現,只需宣告一下就可以使用了。在多處使用只需進行多次宣告,大大提高可複用性。
比如我們的controller,在定義的時候用到一個$scope引數。
app.controller('testC',function($scope){});
如果我們在此處還需操作其他的東西,比如與瀏覽器位址列進行互動。我們只需再多添
一個引數$location進去:
app.controller('testC',function($scope,$location){});
這樣便可以通過$location來與位址列進行互動了,我們僅僅是聲明瞭一下,所需的其他程式碼,框架已經幫我們注入了。我們很明顯的感覺到了這個函式已經不是常規意義上的javascript函數了,在常規的函式中,把形參換一個名字照樣可以執行,但在此處若是把$scope換成別的名字,程式便不能運行了。因為這是已經定義好的服務名稱。
這便是依賴注入機制。順理成章的推斷,我們可以自己定義模組和服務,然後在需要的地方進行宣告,由框架來替我們注入。
來看下我們如何定義一個服務:
app.factory('tpls',function(){
return ['tpl1','tpl2','tpl3','tpl4'];
});
看上去相當簡單,是因為我在這裡僅僅是直接返回一個數組。在實際應用中,這裡應該是需要向伺服器發起一個請求,來獲取到這些模板們。服務的定義方式有好幾種,包括使用provider方法、使用factory方法,使用service方法。它們之間的區別暫且不關心。我們現在只要能建立一個服務出來就可以了。我使用了factory方法。一個需要注意的地方是,框架提供的服務名字都是由$開頭的,所以我們自己定義的最好不要用$開頭,防止發生命名衝突。
定義好一個服務後,我們就可以在控制器中宣告使用了,如下:
app.controller('testC',function($scope,tpls){
$scope.question = questionModel;
$scope.nowTime = new Date().valueOf();
$scope.templates = tpls; //賦值到$scope中
$scope.addOption = function(){
var o = {content:''};
$scope.question.options.push(o);
};
$scope.delOption = function(index){
$scope.question.options.splice(index,1);
};
});
此時,若在模板中書寫如下程式碼,我們便可以獲取到服務tpls所提供的資料了:
模板:
<a href="javascript:void(0);" target="_blank" rel="nofollow">
4.9路由(route)
在談路由機制前有必要先提一下現在比較流行的單頁面應用,就是所謂的single page APP。為了實現無重新整理的檢視切換,我們通常會用ajax請求從後臺取資料,然後套上HTML模板渲染在頁面上,然而ajax的一個致命缺點就是導致瀏覽器後退按鈕失效,儘管我們可以在頁面上放一個大大的返回按鈕,讓使用者點選返回來導航,但總是無法避免使用者習慣性的點後退。解決此問題的一個方法是使用hash,監聽hashchange事件來進行檢視切換,另一個方法是用HTML5的history API,通過pushState()記錄操作歷史,監聽popstate事件來進行檢視切換,也有人把這叫pjax技術。基本流程如下:
如此一來,便形成了通過位址列進行導航的深度連結(deeplinking ),也就是我們所需要的路由機制。通過路由機制,一個單頁應用的各個檢視就可以很好的組織起來了。
4.9.1 ngRoute內容
ng的路由機制是靠ngRoute提供的,通過hash和history兩種方式實現了路由,可以檢測瀏覽器是否支援history來靈活呼叫相應的方式。ng的路由(ngRoute)是一個單獨的模組,包含以下內容:
l 服務$routeProvider用來定義一個路由表,即位址列與檢視模板的對映
l 服務$routeParams儲存了位址列中的引數,例如{id : 1, name : 'tom'}
l 服務$route完成路由匹配,並且提供路由相關的屬性訪問及事件,如訪問當前路由對應的controller
l 指令ngView用來在主檢視中指定載入子檢視的區域
以上內容再加上$location服務,我們就可以實現一個單頁面應用了。下面來看一下具體如何使用這些內容。
4.9.2 ng的路由機制
第一步:引入檔案和依賴
ngRoute模組包含在一個單獨的檔案中,所以第一步需要在頁面上引入這個檔案,如下:
<script src="http://code.angularjs.org/1.2.8/angular.min.js" rel="nofollow"/>
<script src="http://code.angularjs.org/1.2.8/angular-route.min.js" rel="nofollow"/>
光引入還不夠,我們還需在模組宣告中注入對ngRoute的依賴,如下:
var app = angular.module('MyApp', ['ngRoute']);
完成了這些,我們就可以在模板或是controller中使用上面的服務和指令了。下面我們需要定義一個路由表。
第二步:定義路由表
$routeProvider提供了定義路由表的服務,它有兩個核心方法,when(path,route)和otherwise(params),先看一下核心中的核心when(path,route)方法。
when(path,route)方法接收兩個引數,path是一個string型別,表示該條路由規則所匹配的路徑,它將與位址列的內容($location.path)值進行匹配。如果需要匹配引數,可以在path中使用冒號加名稱的方式,如:path為/show/:name,如果位址列是/show/tom,那麼引數name和所對應的值tom便會被儲存在$routeParams中,像這樣:{name : tom}。我們也可以用*進行模糊匹配,如:/show*/:name將匹配/showInfo/tom。
route引數是一個object,用來指定當path匹配後所需的一系列配置項,包括以下內容:
l controller //function或string型別。在當前模板上執行的controller函式,生成新的scope;
l controllerAs //string型別,為controller指定別名;
l template //string或function型別,檢視z所用的模板,這部分內容將被ngView引用;
l templateUrl //string或function型別,當檢視模板為單獨的html檔案或是使用了<script type="text/ng-template">定義模板時使用;
l resolve //指定當前controller所依賴的其他模組;
l redirectTo //重定向的地址。
最簡單情況,我們定義一個html檔案為模板,並初始化一個指定的controller:
function emailRouteConfig($routeProvider){
$routeProvider.when('/show', {
controller: ShowController,
templateUrl: 'show.html'
}).
when('/put/:name',{
controller: PutController,
templateUrl: 'put.html'
});
};
otherwise(params)方法對應路徑匹配不到時的情況,這時候我們可以配置一個redirectTo引數,讓它重定向到404頁面或者是首頁。
第三步:在主檢視模板中指定載入子檢視的位置
我們的單頁面程式都是區域性重新整理的,那這個“區域性”是哪裡呢,這就輪到ngView出馬了,只需在模板中簡單的使用此指令,在哪裡用,哪裡就是“區域性”。例如:
<div ng-view></div> 或:<ng-view></ng-view>
我們的子檢視將會在此處被引入進來。完成這三步後,你的程式的路由就配置好了。
4.9.3 路由示例
下面我們將用一個例子(例09)來說明路由的使用方式及步驟:
1.為demoApp新增一個路由,程式碼如下:
demoApp.config(['$routeProvider',function($routeProvider) {
$routeProvider.when('/list', {
templateUrl: 'route/list.html',
controller: 'routeListController'
}).when('/list/:id', {
templateUrl: 'route/detail.html',
controller: 'routeDetailController'
}).otherwise({
redirectTo: '/list'
});
}]);
/list 對應為:route/list.html頁面,顯示使用者列表;/list/:id對應於route/detail.html頁面,顯示使用者詳細資訊。
2.為list.html和detail.html分別宣告Controller:routeListController和routeDetailController。
demoApp.controller('routeListController',function($scope) {
$scope.users = [{userId:"zhangsan",userName:"張三",userInfo:"我是張三,我為自己帶鹽!"},
{userId:"lisi",userName:"李四",userInfo:"我是李四,我為卿狂!"},
{userId:"woshishui",userName:"我是誰",userInfo:"我是誰!我是誰!我是誰!"}];
});
demoApp.controller('routeDetailController',function($scope, $routeParams, userService) {
$scope.userDetail = userService.getUser($routeParams.id);
});
routeDetailController中如上面提到的一樣,注入了userService服務,在這裡直接拿來用。
3.建立list.html和detail.html頁面,程式碼如下:
<hr/>
<h3>Route : List.html(使用者列表頁面)</h3>
<ul>
<li ng-repeat="user in users">
<a href="http://m.cnblogs.com/142260/3817063.html?full=1#/list/{{ user.userId }}" target="_blank" rel="nofollow">
</li>
</ul>
<hr/>
<h3>Route : detail.html(使用者詳細資訊頁面)</h3>
<h3>使用者名稱:<span style="color: red;">{{userDetail.userName}}</span></h3>
<div>
<span>使用者ID:{{userDetail.userId}}</span><span>使用者名稱:{{userDetail.userName}}</span>
</div>
<div>
使用者簡介:<span>{{userDetail.userInfo}}</span>
</div>
<div>
<a href="http://m.cnblogs.com/142260/3817063.html?full=1#/list" target="_blank" rel="nofollow">返回</a>
</div>
4. 路由區域性重新整理位置:
<h1>AngularJS路由(Route) 示例</h1>
<div ng-view></div>
4.10 NG動畫效果
4.10.1 NG動畫效果簡介
NG動畫效果,現在可以通過CSS3或者是JS來實現,如果是通過JS來實現的話,需要其他JS庫(比如JQuery)來支援,實際上底層實現還是靠其他JS庫,只是NG將其封裝了,
使其更易使用。
NG動畫效果包含以下幾種:
- enter:元素新增到DOM中時執行動畫;
- leave:元素從DOM刪除時執行動畫;
- move:移動元素時執行動畫;
- beforeAddClass:在給元素新增CLASS之前執行動畫;
- addClass:在給元素新增CLASS時執行動畫;
- beforeRemoveClass:在給元素刪除CLASS之前執行動畫;
- removeClass:在給元素刪除CLASS時執行動畫。
其相關引數為:
var ngModule = angular.module('YourApp', ['ngAnimate']);
demoApp.animation('.my-crazy-animation', function() {
return {
enter: function(element, done) {
//run the animation here and call done when the animation is complete
return function(cancelled) {
//this (optional) function will be called when the animation
//completes or when the animation is cancelled (the cancelled
//flag will be set to true if cancelled).
};
},
leave: function(element, done) { },
move: function(element, done) { },
//animation that can be triggered before the class is added
beforeAddClass: function(element, className, done) { },
//animation that can be triggered after the class is added
addClass: function(element, className, done) { },
//animation that can be triggered before the class is removed
beforeRemoveClass: function(element, className, done) { },
//animation that can be triggered after the class is removed
removeClass: function(element, className, done) { }
};
});
4.10.2 動畫效果示例
下面我們來看下DEMO中的例子(例10)。
1.首先,我們在demoApp下定義一個動畫效果,匹配CLASS:” .border-animation”
/*定義動畫*/
demoApp.animation('.border-animation', function(){
return{
beforeAddClass : function (element, className, done) {
$(element).stop().animate({
'border-width':1
},2000, function() {
done();
});
},
removeClass : function (element ,className ,done ) {
$(element).stop().animate({
'border-width':50
},3000, function() {
done();
});
}
};
});
動畫效果的含義就是:在匹配CLASS為border-animation的元素新增一個CLASS之前使其邊框的寬度在2秒內變為1PX;並在其移除一個CLASS時使其邊框的寬度在3秒內變為50PX。
2. 檢視中的程式碼如下(主要,其他相關樣式請檢視例子程式碼):
<div class="border-animation" ng-show="testShow"></div>
<a href="javascript:void(0);" target="_blank" rel="nofollow">
ng-show為false時會為其加上“ng-hide“的CLASS; ng-show為true時會為其移除“ng-hide“的CLASS,從而觸發動畫效果。
3.其他程式碼:
demoApp.controller("test10Controller", function($scope, $animate){
$scope.testShow = true;
});
5 功能演示
略(詳情請看AngularJS/demo WEB演示)
6 AngularJS進階
6.1資料繫結原理研究
Angular使用者都想知道資料繫結是怎麼實現的。你可能會看到各種各樣的詞彙:$watch、$apply、$digest、dirty-checking...它們是什麼?它們是如何工作的呢?這裡我想回答這些問題,其實它們在官方的文件裡都已經回答了,但是我還是想把它們結合在一起來講,但是我只是用一種簡單的方法來講解,如果要想了解技術細節,檢視原始碼。
6.1.1 AngularJS擴充套件事件迴圈
我們的瀏覽器一直在等待事件,比如使用者互動。假如你點選一個按鈕或者在輸入框裡輸入東西,事件的回撥函式就會在javascript直譯器裡執行,然後你就可以做任何DOM操作,等回撥函式執行完畢時,瀏覽器就會相應地對DOM做出變化。(記住,這是個重要的概念),為了解釋什麼是context以及它如何工作,我們還需要解釋更多的概念。
6.1.2 $watch 佇列
每次你繫結一些東西到你的DOM上時你就會往$watch佇列裡插入一條$watch。想象一下$watch就是那個可以檢測它監視的model裡時候有變化的東西。例如你有如下的程式碼:
/*View index.html */
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
在這裡我們有個$scope.user,他被繫結在了第一個輸入框上,還有個$scope.pass,它被繫結在了第二個輸入框上,然後我們在$watch list裡面加入兩個$watch。
再看下面的例子:
/*Controller controllers.js */
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
/*View index.html */
Hello, {{ World }}
這裡,即便我們在$scope上添加了兩個東西,但是隻有一個繫結在了DOM上,因此在這裡只生成了一個$watch。
再看下面的例子:
/*Controller controllers.js */
app.controller('MainCtrl', function($scope) {
$scope.people = [...];
});
/*View index.html */
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>
這裡又生成了多少個$watch呢?每個person有兩個(一個name,一個age),然後ng-repeat又有一個,因此10個person一共是(2 * 10) +1,也就是說有21個$watch。
因此,每一個繫結到了DOM上的資料都會生成一個$watch。
那這寫$watch是什麼時候生成的呢?
當我們的模版載入完畢時,也就是在linking階段(Angular分為compile階段和linking階段),Angular直譯器會尋找每個directive,然後生成每個需要的$watch。
6.1.3 $digest迴圈
還記得我前面提到的擴充套件的事件迴圈嗎?當瀏覽器接收到可以被angular context處理的事件時,$digest迴圈就會觸發。這個迴圈是由兩個更小的迴圈組合起來的。一個處理evalAsync佇列,另一個處理$watch佇列。 這個是處理什麼的呢?$digest將會遍歷我們的$watch,然後詢問:
•嘿,$watch,你的值是什麼?
◦是9。
•好的,它改變過嗎?
◦沒有,先生。
•(這個變數沒變過,那下一個)
•你呢,你的值是多少?
◦報告,是Foo。
•剛才改變過沒?
◦改變過,剛才是Bar。
•(很好,我們有DOM需要更新了)
•繼續詢問直到$watch佇列都檢查過。
這就是所謂的dirty-checking。既然所有的$watch都檢查完了,那就要問了:有沒有$watch更新過?如果有至少一個更新過,這個迴圈就會再次觸發,直到所有的$watch都沒有變化。這樣就能夠保證每個model都已經不會再變化。記住如果迴圈超過10次的話,它將會丟擲一個異常,防止無限迴圈。當$digest迴圈結束時,DOM相應地變化。
例如:
/*Controller controllers.js */
app.controller('MainCtrl', function() {
$scope.name = "Foo";
$scope.changeFoo = function() {
$scope.name = "Bar";
}
});
/*View index.html */
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
這裡我們有一個$watch因為ng-click不生成$watch(函式是不會變的)。
我們可以看出ng的處理流程:
•我們按下按鈕;
•瀏覽器接收到一個事件,進入angular context;
•$digest迴圈開始執行,查詢每個$watch是否變化;
•由於監視$scope.name的$watch報告了變化,它會強制再執行一次$digest迴圈;
•新的$digest迴圈沒有檢測到變化;
•瀏覽器拿回控制權,更新與$scope.name新值相應部分的DOM。
這裡很重要的是每一個進入angular context的事件都會執行一個$digest迴圈,也就是說每次我們輸入一個字母迴圈都會檢查整個頁面的所有$watch。
6.1.4如何進入angular context
誰決定什麼事件進入angular context,而哪些又不進入呢?通過$apply!
如果當事件觸發時,你呼叫$apply,它會進入angular context,如果沒有呼叫就不會進入。現在你可能會問:剛才的例子裡我也沒有呼叫$apply啊,為什麼?Angular已經做了!因此你點選帶有ng-click的元素時,時間就會被封裝到一個$apply呼叫。如果你有一個ng-model="foo"的輸入框,然後你敲一個f,事件就會這樣呼叫$apply("foo = 'f';")。
Angular什麼時候不會自動為我們$apply呢?
這是Angular新手共同的痛處。為什麼我的jQuery不會更新我繫結的東西呢?因為jQuery沒有呼叫$apply,事件沒有進入angular context,$digest迴圈永遠沒有執行。
我們來看一個有趣的例子:
假設我們有下面這個directive和controller。
/*Controller app.js */
app.directive('clickable', function() {
return {
restrict: "E",
scope: {
foo: '=',
bar: '='
},
template: '<ul style="<li>{{foo}}</li><li>{{bar}}</li></ul>',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.foo++;
scope.bar++;
});
}
}
});
app.controller('MainCtrl', function($scope) {
$scope.foo = 0;
$scope.bar = 0;
});
它將foo和bar從controller裡繫結到一個list裡面,每次點選這個元素的時候,foo和bar都會自增1。那我們點選元素的時候會發生什麼呢?我們能看到更新嗎?答案是否定的。因為點選事件是一個沒有封裝到$apply裡面的常見的事件,這意味著我們會失去我們的計數嗎?不會。
真正的結果是:$scope確實改變了,但是沒有強制$digest迴圈,監視foo 和bar的$watch沒有執行。也就是說如果我們自己執行一次$apply那麼這些$watch就會看見這些變化,然後根據需要更新DOM。
執行$apply:
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();
});
$apply是我們的$scope(或者是direcvie裡的link函式中的scope)的一個函式,呼叫它會強制一次$digest迴圈(除非當前正在執行迴圈,這種情況下會丟擲一個異常,這是我們不需要在那裡執行$apply的標誌)。
更好的使用$apply的方法:
element.bind('click', function() {
scope.$apply(function() {
scope.foo++;
scope.bar++;
});
})
有什麼不一樣的?差別就是在第一個版本中,我們是在angular context的外面更新的資料,如果有發生錯誤,Angular永遠不知道。很明顯在這個像個小玩具的例子裡面不會出什麼大錯,但是想象一下我們如果有個alert框顯示錯誤給使用者,然後我們有個第三方的庫進行一個網路呼叫然後失敗了,如果我們不把它封裝進$apply裡面,Angular永遠不會知道失敗了,alert框就永遠不會彈出來了。
因此,如果你想使用一個jQuery外掛,並且要執行$digest迴圈來更新你的DOM的話,要確保你呼叫了$apply。
有時候我想多說一句的是有些人在不得不呼叫$apply時會“感覺不妙”,因為他們會覺得他們做錯了什麼。其實不是這樣的,Angular不是什麼魔術師,他也不知道第三方庫想要更新繫結的資料。
6.1.5使用$watch來監視
你已經知道了我們設定的任何繫結都有一個它自己的$watch,當需要時更新DOM,但是我們如果要自定義自己的watches呢?簡單,來看個例子:
/*Controller app.js */
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = -1;
$scope.$watch('name', function() {
$scope.updated++;
});
});
/*View index.html*/
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
這就是我們創造一個新的$watch的方法。第一個引數是一個字串或者函式,在這裡是只是一個字串,就是我們要監視的變數的名字,在這裡,$scope.name(注意我們只需要
用name)。第二個引數是當$watch說我監視的表示式發生變化後要執行的。我們要知道的第一件事就是當controller執行到這個$watch時,它會立即執行一次,因此我們設定updated為-1。
例子2:
/*Controller app.js */
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = 0;
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
});
/*View index.html*/
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
watch的第二個引數接受兩個引數,新值和舊值。我們可以用他們來略過第一次的執行。通常你不需要略過第一次執行,但在這個例子裡面你是需要的。
例子3:
/*Controller app.js */
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});
/*View index.html*/
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
我們想要監視$scope.user物件裡的任何變化,和以前一樣這裡只是用一個物件來代替前面的字串。
呃?沒用,為啥?因為$watch預設是比較兩個物件所引用的是否相同,在例子1和2裡面,每次更改$scope.name都會建立一個新的基本變數,因此$watch會執行,因為對這個變數的引用已經改變了。在上面的例子裡,我們在監視$scope.user,當我們改變$scope.user.name時,對$scope.user的引用是不會改變的,我們只是每次建立了一個新的$scope.user.name,但是$scope.user永遠是一樣的。
例子4:
/*Controller app.js */
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true );
});
/*View index.html*/
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
現在有用了吧!因為我們對$watch加入了第三個引數,它是一個bool型別的引數,表示的是我們比較的是物件的值而不是引用。由於當我們更新$scope.user.name時$scope.user也會改變,所以能夠正確觸發。
6.1.6 總結
我希望你們已經學會了在Angular中資料繫結是如何工作的。我猜想你的第一印象是dirty-checking很慢,好吧,其實是不對的。它像閃電般快。但是,如果你在一個模版裡有2000-3000個watch,它會開始變慢。但是我覺得如果你達到這個數量級,就可以找個使用者體驗專家諮詢一下了。
無論如何,隨著ECMAScript6的到來,在Angular未來的版本里我們將會有Object.observe那樣會極大改善$digest迴圈的速度。
6.2自定義指令詳解
angular的指令機制。angular通過指令的方式實現了HTML的擴充套件,增強後的HTML不僅長相煥然一新,同時也獲得了很多強大的技能。更厲害的是,你還可以自定義指令,這就意味著HTML標籤的範圍可以擴充套件到無窮大。angular賦予了你造物主的能力。既然是作為angular的精華之一,相應的指令相關的知識也很多的。
6.2.1指令的編譯過程
在開始自定義指令之前,我們有必要了解一下指令在框架中的執行流程:
1.瀏覽器得到 HTML 字串內容,解析得到 DOM 結構。
2.ng 引入,把 DOM 結構扔給 $compile 函式處理:
① 找出 DOM 結構中有變數佔位符;
② 匹配找出 DOM 中包含的所有指令引用;
③ 把指令關聯到 DOM;
④ 關聯到 DOM 的多個指令按權重排列;
⑤ 執行指令中的 compile 函式(改變 DOM 結構,返回 link 函式);
⑥ 得到的所有 link 函式組成一個列表作為 $compile 函式的返回。
3. 執行 link 函式(連線模板的 scope)。
這裡注意區別一下$compile和compile,前者是ng內部的編譯服務,後者是指令中的編譯函式,兩者發揮作用的範圍不同。compile和link函式息息相關又有所區別,這個在後面會講。瞭解執行流程對後面的理解會有幫助。
在這裡有些人可能會問,angular不就是一個js框架嗎,怎麼還能跟編譯扯上呢,又不是像C++那樣的高階語言。其實此編譯非彼編譯,ng編譯的工作是解析指令、繫結監聽器、替換模板中的變數等。因為工作方式很像高階語言編輯中的遞迴、堆疊過程,所以起名為編譯,不要疑惑。
6.2.2指令的使用方式及命名方法
指令的幾種使用方式如下:
- 作為標籤:<my-dir></my-dir>
- 作為屬性:<span my-dir="exp"></span>
- 作為註釋:<!-- directive: my-dir exp -->
- 作為類名:<span class="my-dir: exp;"></span>
其實常用的就是作為標籤和屬性,下面兩種用法目前還沒見過,感覺就是用來賣萌的,姑且留個印象。我們自定義的指令就是要支援這樣的用法。
關於自定義指令的命名,你可以隨便怎麼起名字都行,官方是推薦用[名稱空間-指令名稱]這樣的方式,像ng-controller。不過你可千萬不要用ng-字首了,防止與系統自帶的指令重名。另外一個需知道的地方,指令命名時用駝峰規則,使用時用-分割各單詞。如:定義myDirective,使用時像這樣:<my-directive>。
6.2.3自定義指令的配置引數
下面是定義一個標準指令的示例,可配置的引數包括以下部分:
myModule.directive('namespaceDirectiveName', function factory(injectables) {
var directiveDefinitionObject = {
restrict: string,//指令的使用方式,包括標籤,屬性,類,註釋
priority: number,//指令執行的優先順序
template: string,//指令使用的模板,用HTML字串的形式表示
templateUrl: string,//從指定的url地址載入模板
replace: bool,//是否用模板替換當前元素,若為false,則append在當前元素上
transclude: bool,//是否將當前元素的內容轉移到模板中
scope: bool or object,//指定指令的作用域
controller: function controllerConstructor($scope, $element, $attrs, $transclude){...},//定義與其他指令進行互動的介面函式
require: string,//指定需要依賴的其他指令
link: function postLink(scope, iElement, iAttrs) {...},//以程式設計的方式操作DOM,包
括新增監聽器等
compile: function compile(tElement, tAttrs, transclude){
return: {
pre: function preLink(scope, iElement, iAttrs, controller){...},
post: function postLink(scope, iElement, iAttrs, controller){...}
}
}//程式設計的方式修改DOM模板的副本,可以返回連結函式
};
return directiveDefinitionObject;
});
看上去好複雜的樣子,定義一個指令需要這麼多步驟嘛?當然不是,你可以根據自己的需要來選擇使用哪些引數。事實上priority和compile用的比較少,template和templateUrl又是互斥的,兩者選其一即可。所以不必緊張,接下來分別學習一下這些引數:
l 指令的表現配置引數:restrict、template、templateUrl、replace、transclude;
l 指令的行為配置引數:compile和link;
l 指令劃分作用域配置引數:scope;
l 指令間通訊配置引數:controller和require。
6.2.3指令的表現引數restrict等
指令的表現配置引數:restrict、template、templateUrl、replace、transclude。
我將先從一個簡單的例子開始。
例子的程式碼如下:
var app = angular.module('MyApp', [], function(){console.log('here')});
app.directive('sayHello',function(){
return {
restrict : 'E',
template : '<div>hello</div>'
};
})
然後在頁面中,我們就可以使用這個名為sayHello的指令了,它的作用就是輸出一個hello單詞。像這樣使用:
<say-hello></say-hello>
這樣頁面就會顯示出hello了,看一下生成的程式碼:
<say-hello>
<div>hello</div>
</say-hello>
稍稍解釋一下我們用到的兩個引數,restirct用來指定指令的使用型別,其取值及含義如下:
取值 |
含義 |
使用示例 |
E |
標籤 |
<my-menu title=Products></my-menu> |
A |
屬性 |
<div my-menu=Products></div> |
C |
類 |
<div class="my-menu":Products></div> |
M |
註釋 |
<!--directive:my-menu Products--> |
預設值是A。也可以使用這些值的組合,如EA,EC等等。我們這裡指定為E,那麼它就可以像標籤一樣使用了。如果指定為A,我們使用起來應該像這樣:
<div say-hello></div>
從生成的程式碼中,你也看到了template的作用,它就是描述你的指令長什麼樣子,這部分內容將出現在頁面中,即該指令所在的模板中,既然是模板中,template的內容中也可以使用ng-modle等其他指令,就像在模板中使用一樣。
在上面生成的程式碼中,我們看到了<div>hello</div>外面還包著一層<say-hello>標籤,如果我們不想要這一層多餘的東西了,replace就派上用場了,在配置中將replace賦值為true,將得到如下結構:
<div>hello</div>
replace的作用正如其名,將指令標籤替換為了temple中定義的內容。不寫的話預設為false。
上面的template未免也太簡單了,如果你的模板HTML較複雜,如自定義一個ui元件指令,難道要拼接老長的字串?當然不需要,此時只需用templateUrl便可解決問題。你可以將指令的模板單獨命名為一個html檔案,然後在指令定義中使用templateUrl指定好檔案的路徑即可,如:
templateUrl : ‘helloTemplate.html’
系統會自動發一個http請求來獲取到對應的模板內容。是不是很方便呢,你不用糾結於拼接字串的煩惱了。如果你是一個追求完美的有考慮效能的工程師,可能會發問:那這樣的話豈不是要犧牲一個http請求?這也不用擔心,因為ng的模板還可以用另外一種方式定義,那就是使用<script>標籤。使用起來如下:
<script type="text/ng-template" id="helloTemplate.html">
<div>hello</div>
</script>
你可以把這段程式碼寫在頁面頭部,這樣就不必去請求它了。在實際專案中,你也可以將所有的模板內容集中在一個檔案中,只加載一次,然後根據id來取用。
接下來我們來看另一個比較有用的配置:transclude,定義是否將當前元素的內容轉移到模板中。看解釋有點抽象,不過親手試試就很清楚了,看下面的程式碼(例06):
app.directive('sayHello',function(){
return {
restrict : 'E',
template : '<div>hello,<b ng-transclude></b>!</div>',
replace : true,
transclude : true
};
})
指定了transclude為true,並且template修改了一下,加了一個<b>標籤,並在上面使用了ng-transclude指令,用來告訴指令把內容轉移到的位置。那我們要轉移的內容是什麼呢?請看使用指令時的變化:
<say-hello>美女</say-hello>
內容是什麼你也看到了哈~在執行的時候,美女將會被轉移到<b>標籤中,原來此配置的作用就是——乾坤大挪移!看效果:
hello, 美女!
這個還是很有用的,因為你定義的指令不可能老是那麼簡單,只有一個空標籤。當你需要對指令中的內容進行處理時,此引數便大有可用。
6.2.4指令的行為引數:compile和link
6.2.3中簡單介紹了自定義一個指令的幾個簡單引數,restrict、template、templateUrl、replace、transclude,這幾個理解起來相對容易很多,因為它們只涉及到了表現,而沒有涉及行為。我們繼續學習ng自定義指令的幾個重量級引數:compile和link
l 理解compile和link
不知大家有沒有這樣的感覺,自己定義指令的時候跟寫jQuery外掛有幾分相似之處,都是先預先定義好頁面結構及監聽函式,然後在某個元素上呼叫一下,該元素便擁有了特殊的功能。區別在於,jQuery的側重點是DOM操作,而ng的指令中除了可以進行DOM操作外,更注重的是資料和模板的繫結。jQuery外掛在呼叫的時候才開始初始化,而ng指令在頁面載入進來的時候就被編譯服務($compile)初始化好了。
在指令定義物件中,有compile和link兩個引數,它們是做什麼的呢?從字面意義上看,編譯、連結,貌似太抽象了點。其實可大有內涵,為了在自定義指令的時候能正確使用它們,現在有必要了解一下ng是如何編譯指令的。
l 指令的解析流程詳解
我們知道ng框架會在頁面載入完畢的時候,根據ng-app劃定的作用域來呼叫$compile服務進行編譯,這個$compile就像一個大總管一樣,清點作用域內的DOM元素,看看哪些元素上使用了指令(如<div ng-modle=”m”></div>),或者哪些元素本身就是個指令(如<mydierc></mydirec>),或者使用了插值指令( {{}}也是一種指令,叫interpolation directive),$compile大總管會把清點好的財產做一個清單,然後根據這些指令的優先順序(priority)排列一下,真是個細心的大總管哈~大總管還會根據指令中的配置引數(template,place,transclude等)轉換DOM,讓指令“初具人形”。
然後就開始按順序執行各指令的compile函式,注意此處的compile可不是大總管$compile,人家帶著$是土豪,此處執行的compile函式是我們指令中配置的,compile函式中可以訪問到DOM節點並進行操作,其主要職責就是進行DOM轉換,每個compile函式執行完後都會返回一個link函式,這些link函式會被大總管匯合一下組合成一個合體後的link函式,為了好理解,我們可以把它想象成葫蘆小金剛,就像是進行了這樣的處理。
//合體後的link函式
function AB(){
A(); //子link函式
B(); //子link函式
}
接下來進入link階段,合體後的link函式被執行。所謂的連結,就是把view和scope連結起來。連結成啥樣呢?就是我們熟悉的資料繫結,通過在DOM上註冊監聽器來動態修改scope中的資料,或者是使用$watchs監聽 scope中的變數來修改DOM,從而建立雙向繫結。由此也可以斷定,葫蘆小金剛可以訪問到scope和DOM節點。
不要忘了我們在定義指令中還配置著一個link引數呢,這麼多link千萬別搞混了。那這
個link函式是幹嘛的呢,我們不是有葫蘆小金剛了嘛?那我告訴你,其實它是一個小三。此話怎講?compile函式執行後返回link函式,但若沒有配置compile函式呢?葫蘆小金剛自然就不存在了。
正房不在了,當然就輪到小三出馬了,大總管$compile就把這裡的link函式拿來執行。這就意味著,配置的link函式也可以訪問到scope以及DOM節點。值得注意的是,compile函式通常是不會被配置的,因為我們定義一個指令的時候,大部分情況不會通過程式設計的方式進行DOM操作,而更多的是進行監聽器的註冊、資料的繫結。所以,小三名正言順的被大總管寵愛。
聽完了大總管、葫蘆小金剛和小三的故事,你是不是對指令的解析過程比較清晰了呢?不過細細推敲,你可能還是會覺得情節生硬,有些細節似乎還是沒有透徹的明白,所以還需要再理解下面的知識點:
l compile和link的區別
其實在我看完官方文件後就一直有疑問,為什麼監聽器、資料繫結不能放在compile函式中,而偏偏要放在link函式中?為什麼有了compile還需要link?就跟你質疑我編的故事一樣,為什麼最後小三被寵愛了?所以我們有必要探究一下,compile和link之間到底有什麼區別。好,正房與小三的PK現在開始。
首先是效能。舉個例子:
<ul>
<li ng-repeat="a in array">
<input ng-modle=”a.m” />
</li>
</ul>
我們的觀察目標是ng-repeat指令。假設一個前提是不存在link。大總管$compile在編譯這段程式碼時,會查詢到ng-repeat,然後執行它的compile函式,compile函式根據array的長度複製出n個<li>標籤。而複製出的<li>節點中還有<input>節點並且使用了ng-modle指令,所以compile還要掃描它並匹配指令,然後繫結監聽器。每次迴圈都做如此多的工作。而更加糟糕的一點是,我們會在程式中向array中新增元素,此時頁面上會實時更新DOM,每次有新元素進來,compile函式都把上面的步驟再走一遍,豈不是要累死了,這樣效能必然不行。
現在扔掉那個假設,在編譯的時候compile就只管生成DOM的事,碰到需要繫結監聽器的地方先存著,有幾個存幾個,最後把它們彙總成一個link函式,然後一併執行。這樣就輕鬆多了,compile只需要執行一次,效能自然提升。
另外一個區別是能力。
儘管compile和link所做的事情差不多,但它們的能力範圍還是不一樣的。比如正房能管你的存款,小三就不能。小三能給你初戀的感覺,正房卻不能。
我們需要看一下compile函式和link函式的定義:
function compile(tElement, tAttrs, transclude) { ... }
function link(scope, iElement, iAttrs, controller) { ... }
這些引數都是通過依賴注入而得到的,可以按需宣告使用。從名字也容易看出,兩個函式各自的職責是什麼,compile可以拿到transclude,允許你自己程式設計管理乾坤大挪移的行為。而link中可以拿到scope和controller,可以與scope進行資料繫結,與其他指令進行通訊。兩者雖然都可以拿到element,但是還是有區別的,看到各自的字首了吧?compile拿到的是編譯前的,是從template裡拿過來的,而link拿到的是編譯後的,已經與作用域建立了
關聯,這也正是link中可以進行資料繫結的原因。
我暫時只能理解到這個程度了。實在不想理解這些知識的話,只要簡單記住一個原則就行了:如果指令只進行DOM的修改,不進行資料繫結,那麼配置在compile函式中,如果指令要進行資料繫結,那麼配置在link函式中。
6.2.5指令的劃分作用域引數:scope
我們在上面寫了一個簡單的<say-hello></say-hello>,能夠跟美女打招呼。但是看看人家ng內建的指令,都是這麼用的:ng-model=”m”,ng-repeat=”a in array”,不單單是作為屬性,還可以賦值給它,與作用域中的一個變數繫結好,內容就可以動態變化了。假如我們的sayHello可以這樣用:<say-hello speak=”content”>美女</say-hello>,把要對美女說的話寫在一個變數content中,然後只要在controller中修改content的值,頁面就可以顯示對美女說的不同的話。這樣就靈活多了,不至於見了美女只會說一句hello,然後就沒有然後。
為了實現這樣的功能,我們需要使用scope引數,下面來介紹一下。
使用scope為指令劃分作用域
顧名思義,scope肯定是跟作用域有關的一個引數,它的作用是描述指令與父作用域的關係,這個父作用域是指什麼呢?想象一下我們使用指令的場景,頁面結構應該是這個樣子:
<div ng-controller="testC">
<say-hello speak="content">美女</say-hello>
</div>
外層肯定會有一個controller,而在controller的定義中大體是這個樣子:
var app = angular.module('MyApp', [], function(){console.log('here')});
app.controller('testC',function($scope){
$scope.content = '今天天氣真好!';
});
所謂sayHello的父作用域就是這個名叫testC的控制器所管轄的範圍,指令與父作用域的關係可以有如下取值:
取值 |
說明 |
false |
預設值。使用父作用域作為自己的作用域 |
true |
新建一個作用域,該作用域繼承父作用域 |
javascript物件 |
與父作用域隔離,並指定可以從父作用域訪問的變數 |
乍一看取值為false和true好像沒什麼區別,因為取值為true時會繼承父作用域,即父作用域中的任何變數都可以訪問到,效果跟直接使用父作用域差不多。但細細一想還是有區別的,有了自己的作用域後就可以在裡面定義自己的東西,與跟父作用域混在一起是有本質上的區別。好比是父親的錢你想花多少花多少,可你自己掙的錢父親能花多少就不好說了。你若想看這兩個作用域的區別,可以在link函式中打印出來看看,還記得link函式中可以訪問到scope吧。
最有用的還是取值為第三種,一個物件,可以用鍵值來顯式的指明要從父作用域中使用屬性的方式。當scope值為一個物件時,我們便建立了一個與父層隔離的作用域,不過也不是完全隔離,我們可以手工搭一座橋樑,並放行某些引數。我們要實現對美女說各種話就得靠這個。使用起來像這樣:
scope: {
attributeName1: 'BINDING_STRATEGY',
attributeName2: 'BINDING_STRATEGY',...
}
鍵為屬性名稱,值為繫結策略。等等!啥叫繫結策略?最討厭冒新名詞卻不解釋的行為!別急,聽我慢慢道來。
先說屬性名稱吧,你是不是認為這個attributeName1就是父作用域中的某個變數名稱?錯!其實這個屬性名稱是指令自己的模板中要使用的一個名稱,並不對應父作用域中的變數,稍後的例子中我們來說明。再來看繫結策略,它的取值按照如下的規則:
符號 |
說明 |
舉例 |
@ |
傳遞一個字串作為屬性的值 |
str : ‘@string’ |
= |
使用父作用域中的一個屬性,繫結資料到指令的屬性中 |
name : ‘=username’ |
& |
使用父作用域中的一個函式,可以在指令中呼叫 |
getName : ‘&getUserName’ |
總之就是用符號字首來說明如何為指令傳值。你肯定迫不及待要看例子了,我們結合例子看一下,小二,上栗子~
舉例說明
我想要實現上面想像的跟美女多說點話的功能,即我們給sayHello指令加一個屬性,通過給屬性賦值來動態改變說話的內容 主要程式碼如下:
app.controller('testC',function($scope){
$scope.content = '今天天氣真好!';
});
app.directive('sayHello',function(){
return {
restrict : 'E',
template: '<div>hello,<b ng-transclude></b>,{{ cont }}</div>',
replace : true,
transclude : true,
scope : {
cont : '=speak'
}
};
});
然後在模板中,我們如下使用指令:
<div ng-controller="testC">
<say-hello speak=" content ">美女</say-hello>
</div>
看看執行效果:
美女今天天氣真好!
執行的流程是這樣的:
① 指令被編譯的時候會掃描到template中的{ {cont} },發現是一個表示式;
② 查詢scope中的規則:通過speak與父作用域繫結,方式是傳遞父作用域中的屬性;
③ speak與父作用域中的content屬性繫結,找到它的值“今天天氣真好!”;
④ 將content的值顯示在模板中。
這樣我們說話的內容content就跟父作用域繫結到了一其,如果動態修改父作用域的content的值,頁面上的內容就會跟著改變,正如你點選“換句話”所看到的一樣。
這個例子也太小兒科了吧!簡單雖簡單,但可以讓我們理解清楚,為了檢驗你是不是真的明白了,可以思考一下如何修改指令定義,能讓sayHello以如下兩種方式使用:
<span say-hello speak="content">美女</span>
<span say-hello="content" >美女</span>
答案我就不說了,簡單的很。下面有更重要的事情要做,我們說好了要寫一個真正能用的東西來著。接下來就結合所學到的東西來寫一個摺疊選單,即點選可展開,再點選一次就收縮回去的選單。
控制器及指令的程式碼如下(例07):
app.controller('testC',function($scope){
$scope.title = '個人簡介';
$scope.text = '大家好,我是一名前端工程師,我正在研究AngularJs,歡迎大家與我交流';
});
app.directive('expander',function(){
return {
restrict : 'E',
templateUrl : 'expanderTemp.html',
replace : true,
transclude : true,
scope : {
mytitle : '=etitle'
},
link : function(scope,element,attris){
scope.showText = false;
scope.toggleText = function(){
scope.showText = ! scope.showText;
}
}
};
});
HTML中的程式碼如下:
<script type="text/ng-template" id="expanderTemp.html">
<div class="mybox">
<div class="mytitle" ng-click="toggleText()">
{{mytitle}}
</div>
<div ng-transclude ng-show="showText">
</div>
</div>
</script>
<div ng-controller="testC">
<expander etitle="title">{{text}}</expander>
</div>
還是比較容易看懂的,我只做一點必要的解釋。首先我們定義模板的時候使用了ng的一種定義方式<script type=”text/ng-template”id="expanderTemp.html">,在指令中就可以用templateUrl根據這個id來找到模板。指令中的{{mytitle}}表示式由scope引數指定從etitle傳遞,etitle指向了父作用域中的title。為了實現點選標題能夠展開收縮內容,我們把這部分邏輯放在了link函式中,link函式可以訪問到指令的作用域,我們定義showText屬性來表示內容部分的顯隱,定義toggleText函式來進行控制,然後在模板中繫結好。 如果把showText和toggleText定義在controller中,作為$scope的屬性呢?顯然是不行的,這就是隔離作用域的意義所在,父作用域中的東西除了title之外通通被遮蔽。
上面的例子中,scope引數使用了=號來指定獲取屬性的型別為父作用域的屬性,如果我們想在指令中使用父作用域中的函式,使用&符號即可,是同樣的原理。
6.2.6指令間通訊引數:controller和require
使用指令來定義一個ui元件是個不錯的想法,首先使用起來方便,只需要一個標籤或者屬性就可以了,其次是可複用性高,通過controller可以動態控制ui元件的內容,而且擁有雙向繫結的能力。當我們想做的元件稍微複雜一點,就不是一個指令可以搞定的了,就需要指令與指令的協作才可以完成,這就需要進行指令間通訊。
想一下我們進行模組化開發的時候的原理,一個模組暴露(exports)對外的介面,另外一個模組引用(require)它,便可以使用它所提供的服務了。ng的指令間協作也是這個原理,這也正是自定義指令時controller引數和require引數的作用。
controller引數用於定義指令對外提供的介面,它的寫法如下:
controller: function controllerConstructor($scope, $element, $attrs, $transclude)
它是一個構造器函式,將來可以構造出一個例項傳給引用它的指令。為什麼叫controller(控制器)呢?其實就是告訴引用它的指令,你可以控制我。至於可以控制那些東西呢,就需要在函式體中進行定義了。先看controller可以使用的引數,作用域、節點、節點的屬性、節點內容的遷移,這些都可以通過依賴注入被傳進來,所以你可以根據需要只寫要用的引數。關於如何對外暴露介面,我們在下面的例子來說明。
require引數便是用來指明需要依賴的其他指令,它的值是一個字串,就是所依賴的指令的名字,這樣框架就能按照你指定的名字來從對應的指令上面尋找定義好的controller了。不過還稍稍有點特別的地方,為了讓框架尋找的時候更輕鬆些,我們可以在名字前面加個小小的字首:^,表示從父節點上尋找,使用起來像這樣:require : ‘^directiveName’,如果不加,$compile服務只會從節點本身尋找。另外還可以使用字首:?,此字首將告訴$compile服務,如果所需的controller沒找到,不要丟擲異常。
所需要了解的知識點就這些,接下來是例子時間,依舊是從書上抄來的一個例子,我們要做的是一個手風琴選單,就是多個摺疊選單並列在一起,此例子用來展示指令間的通訊再合適不過。
首先我們需要定義外層的一個結構,起名為accordion,程式碼如下:
app.directive('accordion',function(){
return {
restrict : 'E',
template : '<div ng-transclude></div>',
replace : true,
transclude : true,
controller :function(){
var expanders = [];
this.gotOpended = function(selectedExpander){
angular.forEach(expanders,function(e){
if(selectedExpander != e){
e.showText = false;
}
});
}
this.addExpander = function(e){
expanders.push(e);
}
}
}
});
需要解釋的只有controller中的程式碼,我們定義了一個摺疊選單陣列expanders,並且通過this關鍵字來對外暴露介面,提供兩個方法。gotOpended接受一個selectExpander引數用來修改陣列中對應expander的showText屬性值,從而實現對各個子選單的顯隱控制。addExpander方法對外提供向expanders陣列增加元素的介面,這樣在子選單的指令中,便可以呼叫它把自身加入到accordion中。
看一下我們的expander需要做怎樣的修改呢:
app.directive('expander',function(){
return {
restrict : 'E',
templateUrl : 'expanderTemp.html',
replace : true,
transclude : true,
require : '^?accordion',
scope : {
title : '=etitle'
},
link : function(scope,element,attris,accordionController){
scope.showText = false;
accordionController.addExpander(scope);
scope.toggleText = function(){
scope.showText = ! scope.showText;
accordionController.gotOpended(scope);
}
}
};
});
首先使用require引數引入所需的accordion指令,新增?^字首表示從父節點查詢並且失敗後不丟擲異常。然後便可以在link函式中使用已經注入好的accordionController了,呼叫addExpander方法將自己的作用域作為引數傳入,以供accordionController訪問其屬性。然
後在toggleText方法中,除了要把自己的showText修改以外,還要呼叫accordionController的gotOpended方法通知父層指令把其他選單給收縮起來。
指令定義好後,我們就可以使用了,使用起來如下:
<accordion>
<expander ng-repeat="expander in expanders" etitle="expander.title">
{{expander.text}}
</expander>
</accordion>
外層使用了accordion指令,內層使用expander指令,並且在expander上用ng-repeat迴圈輸出子選單。請注意這裡遍歷的陣列expanders可不是accordion中定義的那個expanders,如果你這麼認為了,說明還是對作用域不夠了解。此expanders是ng-repeat的值,它是在外層controller中的,所以,在testC中,我們需要新增如下資料:
$scope.expanders = [
{title: '個人簡介',
text: '大家好,我是一名前端工程師,我正在研究AngularJs,歡迎大家與我交流'},
{title: '我的愛好',
text: 'LOL '},
{title: '性格',
text: ' 我的性格就是無性格'}
];
6.3 效能及調優
6.3.1效能測試
AnglarJS作為一款優秀的Web框架,可大大簡化前端開發的負擔。
AnglarJS很棒,但當處理包含複雜資料結構的大型列表時,其執行速度就會非常慢。
這是我們將核心管理頁面遷移到AngularJS過程中遇到的問題。這些頁面在顯示500行資料時本應該工作順暢,但首個方法的渲染時間竟花費了7秒,太可怕了。後來,我們發現了在實現過程中存在兩個主要效能問題。一個與“ng-repeat ”指令有關,另一個與過濾器有關。
AngularJS 中的ng-repeat在處理大型列表時,速度為什麼會變慢?
AngularJS中的ng-repeat在處理2500個以上的雙向資料繫結時速度會變慢。這是由於AngularJS通過“dirty checking”函式來檢測變化。每次檢測都會花費時間,所以包含複雜資料結構的大型列表將降低你應用的執行速度。
提高效能的先決條件
時間記錄指令
為了測量一個列表渲染所花費的時間,我們寫了一個簡單的程式,通過使用“ng-repeat”的屬性“$last”來記錄時間。時間存放在TimeTracker服務中,這樣時間記錄就與伺服器端的資料載入分開了。
// Post repeat directive for logging the rendering time
angular.module('siApp.services').directive('postRepeatDirective',
['$timeout', '$log', 'TimeTracker',
function($timeout, $log, TimeTracker) {
return function(scope, element, attrs) {
if (scope.$last){
$timeout(function(){
var timeFinishedLoadingList = TimeTracker.reviewListLoaded();
var ref = new Date(timeFinishedLoadingList);
var end = new Date();
$log.debug("## DOM rendering list took: " + (end - ref) + " ms");
});
}
};
}
]);
// Use in HTML:
<tr ng-repeat="item in items" post-repeat-directive>…</tr>
Chrome開發者工具的時間軸(Timeline)屬性
在Chrome開發者工具的時間軸標籤中,你可以看見事件、每秒內瀏覽器幀數和記憶體分配。“memory”工具用來檢測記憶體洩漏,及頁面所需的記憶體。當幀速率每秒低於30幀時就會出現頁面閃爍問題。“frames”工具可幫助瞭解渲染效能,還可顯示出一個JavaScript任務所花費的CPU時間。
通過限制列表的大小進行基本的調優
緩解該問題,最好的辦法是限制所顯示列表的大小。可通過分頁、新增無限滾動條來實現。
分頁,我們可以使用AngularJS的“limitTo”過濾器(AngularJS1.1.4版本以後)和“startFrom”過濾器。可以通過限制顯示列表的大小來減少渲染時間。這是減少渲染時間最高效的方法。
6.3.2七大調優法則
1.渲染沒有資料繫結的列表
這是最明顯的解決方案,因為資料繫結是效能問題最可能的根源。如果你只想顯示一次列表,並不需要更新、改變資料,放棄資料繫結是絕佳的辦法。不過可惜的是,你會失去對資料的控制權,但除了該法,我們別無選擇。
2.不要使用內聯方法計算資料
為了在控制器中直接過濾列表,不要使用可獲得過濾連結的方法。“ng-repeat”會評估每個表示式。在我們的案例中,“filteredItems()”返回過濾連結。如果評估過程很慢,它將迅速降低整個應用的速度。
l <li ng-repeat="item in filteredItems()"> //這並不是一個好方法,因為要頻繁地評估。
l <li ng-repeat="item in items"> //這是要採用的方法
3.使用兩個列表(一個用來進行檢視顯示,一個作為資料來源)
將要顯示的列表與總的資料列表分開,是非常有用的模型。你可以對一些過濾進行預處理,並將存於快取中的連結應用到檢視上。下面案例展示了基本實現過程。filteredLists變數儲存著快取中的連結,applyFilter方法來處理對映。
/* Controller */
// Basic list
var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}];
// Init displayedList
$scope.displayedItems = items;
// Filter Cache
var filteredLists['active'] = $filter('filter)(items, {"active" : true});
// Apply the filter
$scope.applyFilter = function(type) {
if (filteredLists.hasOwnProperty(type){ // Check if filter is cached
$scope.displayedItems = filteredLists[type];
} else {
/* Non cached filtering */
}
}
// Reset filter
$scope.resetFilter = function() {
$scope.displayedItems = items;
}
/* View */
<button ng-click="applyFilter('active')">Select active</button>
<ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>
4.在其他模板中使用ng-if來代替ng-show
如果你用指令、模板來渲染額外的資訊,例如通過點選來顯示列表項的詳細資訊,一定要使用 ng-if(AngularJSv. 1.1.5以後)。ng-if可阻止渲染(與ng-show相比)。所以其它DOM和資料繫結可根據需要進行評估。
<li ng-repeat="item in items">
<p> {{ item.title }} </p>
<button ng-click="item.showDetails = !item.showDetails">Show details</buttons>
<div ng-if="item.showDetails">
{{item.details}}
</div>
</li>
5.不要使用ng-mouseenter、ng-mouseleave等指令
使用內部指令,像ng-mouseenter,AngularJS會使你的頁面閃爍。瀏覽器的幀速率通常低於每秒30幀。使用jQuery建立動畫、滑鼠懸浮效果可以解決該問題。確保將滑鼠事件放入jQuery的.live()函式中。
6.關於過濾的小提示:通過ng-show隱藏多餘的元素
對於長列表,使用過濾同樣會減低工作效率,因為每個過濾都會建立一個原始列表的子連結。在很多情況下,資料沒有變化,過濾結果也會保持不變。所以對資料列表進行預過濾,並根據情況將它應用到檢視中,會大大節約處理時間。
在ng-repeat指令中使用過濾器,每個過濾器會返回一個原始連結的子集。AngularJS 從DOM中移除多餘元素(通過呼叫 $destroy),同時也會從$scope中移除他們。當過濾器的輸入發生改變時,子集也會隨著變化,元素必須進行重新連結,或著再呼叫$destroy。
大部分情況下,這樣做很好,但一旦使用者經常過濾,或者列表非常巨大,不斷的連結與
銷燬將影響效能。為了加快過濾的速度,你可以使用ng-show和ng-hide指令。在控制器中,進行過濾,併為每項新增一個屬性。依靠該屬性來觸發ng-show。結果是,只為這些元素增加ng-hide類,來代替將它們移除子列表、$scope和DOM。
觸發ng-show的方法之一是使用表示式語法。ng-show的值由表示式語法來確定。可以看下面的例子:
<input ng-model="query"></input>
<li ng-repeat="item in items" ng-show="([item.name] | filter:query).length"> {{item.name}} </li>
<span style="font-size: 14px; line-height: 24px; font-family:; white-space: normal;"></span>
7.關於過濾的小提示:防抖動輸入
解決第6點提出的持續過濾問題的另一個方法是防抖動使用者輸入。例如,如果使用者輸入一個搜尋關鍵詞,只當使用者停止輸入後,過濾器才會被啟用。使用該防抖動服務的一個很好的解決方案請見: http://jsfiddle.net/Warspawn/6K7Kd/。將它應用到你的檢視及控制器中,如下所示:
/* Controller */
// Watch the queryInput and debounce the filtering by 350 ms.
$scope.$watch('queryInput', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$debounce(applyQuery, 350);
});
var applyQuery = function() {
$scope.filter.query = $scope.query;
};
/* View */
<input ng-model="queryInput"/>
<li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>
7 總結
angular上手比較難,初學者(特別是習慣了使用JQuery的人)可能不太適應其語法以及思想。隨著對ng探索的一步步深入,也確實感覺到了這一點,尤其是框架內部的某些執行機制。
7.1頁面效果
ng-show ng-hide 無動畫效果問題
7.2委派事件(代理事件)
7.2.1 NG迴圈及事件繫結
<ul>
<li ng-repeat="a in array">
<input ng-modle=”a.m” />
</li>
</ul>
Ng會根據array的長度複製出n個<li>標籤。而複製出的<li>節點中還有<input>節點並且使用了ng-modle指令,所以ng會對所有的<input>繫結監聽器(事件)。如果array很大,就會繫結太多的事件,效能出現問題。
7.2.2 jQuery委派事件
從jQuery1.7開始,提供了.on()附加事件處理程式。
.on( events [, selector ] [, data ], handler(eventObject) )
引數Selector為一個選擇器字串,用於過濾出被選中的元素中能觸發事件的後代元素。如果選擇器是 null 或者忽略了該選擇器,那麼被選中的元素總是能觸發事件。
如果省略selector或者是null,那麼事件處理程式被稱為直接事件 或者 直接繫結事件 。每次選中的元素觸發事件時,就會執行處理程式,不管它直接繫結在元素上,還是從後代(內部)元素冒泡到該元素的。
當提供selector引數時,事件處理程式是指為委派事件(代理事件)。事件不會在直接繫結的元素上觸發,但當selector引數選擇器匹配到後代(內部元素)的時候,事件處理函式才會被觸發。jQuery 會從 event target 開始向上層元素(例如,由最內層元素到最外層元素)開始冒泡,並且在傳播路徑上所有綁定了相同事件的元素若滿足匹配的選擇器,那麼這些元素上的事件也會被觸發。
委託事件有兩個優勢:他們能在後代元素新增到文件後,可以處理這些事件;代理事件的另一個好處就是,當需要監視很多元素的時候,代理事件的開銷更小。
例如,在一個表格的 tbody 中含有 1,000 行,下面這個例子會為這 1,000 元素繫結事
$("#dataTable tbody tr").on("click", function(event){ alert($(this).text());});
委派事件的方法只有一個元素的事件處理程式,tbody,並且事件只會向上冒泡一層(從被點選的tr 到 tbody ):
$("#dataTable tbody").on("click", "tr", function(event){ alert($(this).text());});
許多委派的事件處理程式繫結到 document 樹的頂層附近,可以降低效能。每次發生事件時,jQuery 需要比較從 event target(目標元素) 開始到文件頂部的路徑中每一個元素上所有該型別的事件。為了獲得更好的效能,在繫結代理事件時,繫結的元素最好儘可能的靠近目標元素。避免在大型文件中,過多的在 document 或 document.body 上新增代理事件。