來自:http://www.jb51.net/article/80454.htmjavascript
Angular JS (Angular.JS) 是一組用來開發Web頁面的框架、模板以及數據綁定和豐富UI組件。它支持整個開發進程,提供web應用的架構,無需進行手工DOM操做。 AngularJS很小,只有60K,兼容主流瀏覽器,與 jQuery 配合良好。雙向數據綁定多是AngularJS最酷最實用的特性,將MVC的原理展示地淋漓盡致.html
AngularJS的工做原理是:HTML模板將會被瀏覽器解析到DOM中, DOM結構成爲AngularJS編譯器的輸入。AngularJS將會遍歷DOM模板, 來生成相應的NG指令,全部的指令都負責針對view(即HTML中的ng-model)來設置數據綁定。所以, NG框架是在DOM加載完成以後, 纔開始起做用的.java
在html中:web
1
2
3
4
5
6
7
|
<
body
ng-app
=
"ngApp"
>
<
div
ng-controller
=
"ngCtl"
>
<
label
ng-model
=
"myLabel"
></
label
>
<
input
type
=
"text"
ng-model
=
"myInput"
/>
<
button
ng-model
=
"myButton"
ng-click
=
"btnClicked"
></
button
>
</
div
>
</
body
>
|
在js中:瀏覽器
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// angular app
var
app = angular.module(
"ngApp"
, [],
function
(){
console.log(
"ng-app : ngApp"
);
});
// angular controller
app.controller(
"ngCtl"
, [
'$scope'
,
function
($scope){
console.log(
"ng-controller : ngCtl"
);
$scope.myLabel =
"text for label"
;
$scope.myInput =
"text for input"
;
$scope.btnClicked =
function
() {
console.log(
"Label is "
+ $scope.myLabel);
}
}]);
|
如上,咱們在html中先定義一個angular的app,指定一個angular的controller,則該controller會對應於一個做用域(能夠用$scope前綴來指定做用域中的屬性和方法等). 則在該ngCtl的做用域內的HTML標籤, 其值或者操做均可以經過$scope的方式跟js中的屬性和方法進行綁定.架構
這樣, 就實現了NG的雙向數據綁定: 即HTML中呈現的view與AngularJS中的數據是一致的. 修改其一, 則對應的另外一端也會相應地發生變化.app
這樣的方式,使用起來真的很是方便. 咱們僅關心HTML標籤的樣式, 及其對應在js中angular controller做用域下綁定的屬性和方法. 僅此而已, 將衆多複雜的DOM操做全都省略掉了.框架
這樣的思想,其實跟jQuery的DOM查詢和操做是徹底不同的, 所以也有不少人建議用AngularJS的時候,不要混合使用jQuery. 固然, 兩者各有優劣, 使用哪一個就要看本身的選擇了.函數
NG中的app至關於一個模塊module, 在每一個app中能夠定義多個controller, 每一個controller都會有各自的做用域空間,不會相互干擾.性能
綁定數據是怎樣生效的
初學AngularJS的人可能會踩到這樣的坑,假設有一個指令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var
app = angular.module(
"test"
, []);
app.directive(
"myclick"
,
function
() {
return
function
(scope, element, attr) {
element.on(
"click"
,
function
() {
scope.counter++;
});
};
});
app.controller(
"CounterCtrl"
,
function
($scope) {
$scope.counter = 0;
});
<body ng-app=
"test"
>
<div ng-controller=
"CounterCtrl"
>
<button myclick>increase</button>
<span ng-bind=
"counter"
></span>
</div>
</body>
|
這個時候,點擊按鈕,界面上的數字並不會增長。不少人會感到迷惑,由於他查看調試器,發現數據確實已經增長了,Angular不是雙向綁定嗎,爲何數據變化了,界面沒有跟着刷新?
試試在scope.counter++;這句以後加一句scope.digest();再看看是否是好了?
爲何要這麼作呢,什麼狀況下要這麼作呢?咱們發現第一個例子中並無digest,並且,若是你寫了digest,它還會拋出異常,說正在作其餘的digest,這是怎麼回事?
咱們先想一想,假如沒有AngularJS,咱們想要本身實現這麼個功能,應該怎樣?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
<!DOCTYPE html>
<html>
<head>
<meta charset=
"utf-8"
/>
<title>two-way binding</title>
</head>
<body onload=
"init()"
>
<button ng-click=
"inc"
>
increase 1
</button>
<button ng-click=
"inc2"
>
increase 2
</button>
<span style=
"color:red"
ng-bind=
"counter"
></span>
<span style=
"color:blue"
ng-bind=
"counter"
></span>
<span style=
"color:green"
ng-bind=
"counter"
></span>
<script type=
"text/javascript"
>
/* 數據模型區開始 */
var
counter = 0;
function
inc() {
counter++;
}
function
inc2() {
counter+=2;
}
/* 數據模型區結束 */
/* 綁定關係區開始 */
function
init() {
bind();
}
function
bind() {
var
list = document.querySelectorAll(
"[ng-click]"
);
for
(
var
i=0; i<list.length; i++) {
list[i].onclick = (
function
(index) {
return
function
() {
window[list[index].getAttribute(
"ng-click"
)]();
apply();
};
})(i);
}
}
function
apply() {
var
list = document.querySelectorAll(
"[ng-bind='counter']"
);
for
(
var
i=0; i<list.length; i++) {
list[i].innerHTML = counter;
}
}
/* 綁定關係區結束 */
</script>
</body>
</html>
|
能夠看到,在這麼一個簡單的例子中,咱們作了一些雙向綁定的事情。從兩個按鈕的點擊到數據的變動,這個很好理解,但咱們沒有直接使用DOM的onclick方法,而是搞了一個ng-click,而後在bind裏面把這個ng-click對應的函數拿出來,綁定到onclick的事件處理函數中。爲何要這樣呢?由於數據雖然變動了,可是尚未往界面上填充,咱們須要在此作一些附加操做。
從另一個方面看,當數據變動的時候,須要把這個變動應用到界面上,也就是那三個span裏。但因爲Angular使用的是髒檢測,意味着當改變數據以後,你本身要作一些事情來觸發髒檢測,而後再應用到這個數據對應的DOM元素上。問題就在於,怎樣觸發髒檢測?何時觸發?
咱們知道,一些基於setter的框架,它能夠在給數據設值的時候,對DOM元素上的綁定變量做從新賦值。髒檢測的機制沒有這個階段,它沒有任何途徑在數據變動以後當即獲得通知,因此只能在每一個事件入口中手動調用apply(),把數據的變動應用到界面上。在真正的Angular實現中,這裏先進行髒檢測,肯定數據有變化了,而後纔對界面設值。
因此,咱們在ng-click裏面封裝真正的click,最重要的做用是爲了在以後追加一次apply(),把數據的變動應用到界面上去。
那麼,爲何在ng-click裏面調用$digest的話,會報錯呢?由於Angular的設計,同一時間只容許一個$digest運行,而ng-click這種內置指令已經觸發了$digest,當前的尚未走完,因此就出錯了。
$digest和$apply
在Angular中,有$apply和$digest兩個函數,咱們剛纔是經過$digest來讓這個數據應用到界面上。但這個時候,也能夠不用$digest,而是使用$apply,效果是同樣的,那麼,它們的差別是什麼呢?
最直接的差別是,$apply能夠帶參數,它能夠接受一個函數,而後在應用數據以後,調用這個函數。因此,通常在集成非Angular框架的代碼時,能夠把代碼寫在這個裏面調用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var
app = angular.module(
"test"
, []);
app.directive(
"myclick"
,
function
() {
return
function
(scope, element, attr) {
element.on(
"click"
,
function
() {
scope.counter++;
scope.$apply(
function
() {
scope.counter++;
});
});
};
});
app.controller(
"CounterCtrl"
,
function
($scope) {
$scope.counter = 0;
});
|
除此以外,還有別的區別嗎?
在簡單的數據模型中,這二者沒有本質差異,可是當有層次結構的時候,就不同了。考慮到有兩層做用域,咱們能夠在父做用域上調用這兩個函數,也能夠在子做用域上調用,這個時候就能看到差異了。
對於$digest來講,在父做用域和子做用域上調用是有差異的,可是,對於$apply來講,這二者同樣。咱們來構造一個特殊的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
var
app = angular.module(
"test"
, []);
app.directive(
"increasea"
,
function
() {
return
function
(scope, element, attr) {
element.on(
"click"
,
function
() {
scope.a++;
scope.$digest();
});
};
});
app.directive(
"increaseb"
,
function
() {
return
function
(scope, element, attr) {
element.on(
"click"
,
function
() {
scope.b++;
scope.$digest();
//這個換成$apply便可
});
};
});
app.controller(
"OuterCtrl"
, [
"$scope"
,
function
($scope) {
$scope.a = 1;
$scope.$watch(
"a"
,
function
(newVal) {
console.log(
"a:"
+ newVal);
});
$scope.$on(
"test"
,
function
(evt) {
$scope.a++;
});
}]);
app.controller(
"InnerCtrl"
, [
"$scope"
,
function
($scope) {
$scope.b = 2;
$scope.$watch(
"b"
,
function
(newVal) {
console.log(
"b:"
+ newVal);
$scope.$emit(
"test"
, newVal);
});
}]);
<div ng-app=
"test"
>
<div ng-controller=
"OuterCtrl"
>
<div ng-controller=
"InnerCtrl"
>
<button increaseb>increase b</button>
<span ng-bind=
"b"
></span>
</div>
<button increasea>increase a</button>
<span ng-bind=
"a"
></span>
</div>
</div>
|
這時候,咱們就能看出差異了,在increase b按鈕上點擊,這時候,a跟b的值其實都已經變化了,可是界面上的a沒有更新,直到點擊一次increase a,這時候剛纔對a的累加纔會一次更新上來。怎麼解決這個問題呢?只需在increaseb這個指令的實現中,把$digest換成$apply便可。
當調用$digest的時候,只觸發當前做用域和它的子做用域上的監控,可是當調用$apply的時候,會觸發做用域樹上的全部監控。
所以,從性能上講,若是能肯定本身做的這個數據變動所形成的影響範圍,應當儘可能調用$digest,只有當沒法精確知道數據變動形成的影響範圍時,纔去用$apply,很暴力地遍歷整個做用域樹,調用其中全部的監控。
從另一個角度,咱們也能夠看到,爲何調用外部框架的時候,是推薦放在$apply中,由於只有這個地方纔是對全部數據變動都應用的地方,若是用$digest,有可能臨時丟失數據變動。
髒檢測的利弊
不少人對Angular的髒檢測機制感到不屑,推崇基於setter,getter的觀測機制,在我看來,這只是同一個事情的不一樣實現方式,並無誰徹底賽過誰,二者是各有優劣的。
你們都知道,在循環中批量添加DOM元素的時候,會推薦使用DocumentFragment,爲何呢,由於若是每次都對DOM產生變動,它都要修改DOM樹的結構,性能影響大,若是咱們能先在文檔碎片中把DOM結構建立好,而後總體添加到主文檔中,這個DOM樹的變動就會一次完成,性能會提升不少。
同理,在Angular框架裏,考慮到這樣的場景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function
TestCtrl($scope) {
$scope.numOfCheckedItems = 0;
var
list = [];
for
(
var
i=0; i<10000; i++) {
list.push({
index: i,
checked:
false
});
}
$scope.list = list;
$scope.toggleChecked =
function
(flag) {
for
(
var
i=0; i<list.length; i++) {
list[i].checked = flag;
$scope.numOfCheckedItems++;
}
};
}
|
若是界面上某個文本綁定這個numOfCheckedItems,會怎樣?在髒檢測的機制下,這個過程毫無壓力,一次作完全部數據變動,而後總體應用到界面上。這時候,基於setter的機制就慘了,除非它也是像Angular這樣把批量操做延時到一次更新,不然性能會更低。
因此說,兩種不一樣的監控方式,各有其優缺點,最好的辦法是瞭解各自使用方式的差別,考慮出它們性能的差別所在,在不一樣的業務場景中,避開最容易形成性能瓶頸的用法。