来自: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这样把批量操做延时到一次更新,不然性能会更低。
因此说,两种不一样的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差别,考虑出它们性能的差别所在,在不一样的业务场景中,避开最容易形成性能瓶颈的用法。