Data-binding is an automatic way of updating the view whenever the model changes, as well as updating the model whenever the view changes. This is awesome because it eliminates DOM manipulation from the list of things you have to worry about.
在AngularJS中,我们可以只用{{}}
表达式或ng-bind``ng-model
等指令就能很轻易的实现数据的绑定,而不是用DOM操作。
AngularJS会自动更新scope
模型当view
视图发生变化的时候或是更新view当scope发生变化。它很cool,but how does it work?
How it work?
AngularJS数据绑定机制依赖于它的三个拥有强大功能的函数$watch()
、$digest()
、$apply()
$watch()
使用$watch(),就可以添加一个监听器(watcher)。监听器的作用是监听值的变化,当值发生变化会收到提示。
$scope.$watch('myname', function(newValue, oldValue) {
//使用newValue 更新DOM
});
$watch()创建的时候需要指定两个参数。监控函数
、监听函数
-
上面的表达式
myname
会被AngularJS解析成监控函数
。 -
AngularJS会检查当前
监控函数
的返回值并且和上一个监控函数
的值进行比较,当值发生变化的时候监听器会被标记为脏的。这就是脏值检查(dirty-checking)
。 -
脏值检查看似简单效率低下,但是在语义上始终是正确的
While dirty-checking may seem simple, and even inefficient, it turns out that it is semantically correct all the time
1.1 当我们编写了一个表达式{{}}
或ng-model
2.2 AngularJS会在scope上创建一个监听器,当scope的值发生变化,它会更新视图。
3.3 同样我们也可以手动添加监听器
-
只监听必要的变量
-
尽可能使$watch中的运算简单,在单个$watch中进行繁杂的运算将使得整个应用变慢
<button ng-click="changeAge()">Change</button>
<script>
angular.module('myApp',[])
.controller("myCtrl",function($scope){
$scope.myAge = 16;
$scope.$watch("myAge",function(newValue, oldValue){
console.log("My age change! old age:"+oldValue+",new age:"+newValue);
});
$scope.changeAge = function(){
$scope.myAge ++;
}
});
console
:My age change! old age:16,new age:16
,My age change! old age:16,new age:17
$digest()
$digest()函数会循环并遍历其监视的所有scope及其子scope,它会触发每一个监听器中的监控函数,如果监听器是脏的,就会执行对应的监控函数。
-
当触发UI事件、ajax请求或是timeout等延时事件修改模型后,AngularJS会自动触发一次$digest()循环
-
$digest()通常不会只执行一次,当前循环结束后,如果有监控的值变更了,又存在脏东西,则会再运行第二次。只到所有监听的值不再发生变化。
$apply()
AngularJS并不会直接调用$digest()。它调用通过调用$scope.$apply()
,而$scope.$apply()会调用$rootScope.$digest()
。因此一个$digest()开始于$rootScope
,随后会访问所有child scopes并遍历所有的监听器。
-
我们可以执行一些与Angular无关的代码,这些代码也还是可以改变scope,$apply可以保证作用域上的监听器可以检测这些变更。
-
如果你更改了AngularJS上下文之外的任何scope,那么你需要手动调用 $apply()来通知更改AngularJS,你正在改变一些模型,它应该触发检查。
手动调用$apply()
在上面demo的基础上我们调用了setTimeout
函数,让myAge++
以后,2s
后再变为16
。
<span ng-bind="myAge"></span>
<button ng-click="changeAge()">Change</button>
<script>
angular.module('myApp',[])
.controller("myCtrl",function($scope){
$scope.myAge = 16;
$scope.$watch("myAge",function(newValue, oldValue){
console.log("My age change! old age:"+oldValue+",new age:"+newValue);
});
$scope.changeAge = function(){
$scope.myAge ++;
setTimeout(function() {
$scope.myAge = 16;
console.log($scope.myAge);
}, 2000);
}
});
我们可以看到console
,
1.1 页面加载完成后
My age change! old age:16,new age:16
2.2 点击按钮后
My age change! old age:16,new age:16
My age change! old age:16,new age:17
16
setTimeout
以后并没有触发$watch()
函数。只是因为我们在AngularJS库之外的函数修改了scope。AngularJS无法感知,我们需要调用apply()来通知它。
3.3 手动调用apply()
setTimeout(function() {
$scope.$apply(function(){
$scope.myAge = 16;
console.log($scope.myAge);
});
}, 2000);
4.4 使用AngularJS内置服务$timeout
angular.module('myApp',[])
.controller("myCtrl",["$scope","$timeout",function($scope,$timeout){
$scope.myAge = 16;
$scope.$watch("myAge",function(newValue, oldValue){
console.log("My age change! old age:"+oldValue+",new age:"+newValue);
});
$scope.changeAge = function(){
$scope.myAge ++;
$timeout(function() {
$scope.myAge = 16;
console.log($scope.myAge);
}, 2000);
}
}]);
另附一张stackoverflow
上的图
$observe和$watch区别
我们用stackoverflow
上的一个例子简单看一下AngularJS中$observe和$watch区别。
-
$watch
用来观察表达式,可以是function
或是String
字符串 -
$observe
不同在于它是Attrbutes
对象的一个方法,它用于观察DOM的attribute
变化。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body ng-controller="myCtrl">
<div ng-app="App" ng-controller="Ctrl" class="app">
<div d1 attr1="{{prop1}}-12" attr2="prop2" attr3="33" attr4="'a_string'" attr5="a_string" attr6="{{1+aNumber}}"></div>
<script>
angular.module("App", []).controller("Ctrl", ["$scope", function($scope) {
$scope.prop1 = 'scope_prop1';
$scope.prop2 = 'scope_prop2';
$scope.aNumber = 44;
$scope.obj = {
prop1: "obj_prop1"
}
$scope.changeProperties = function() {
$scope.prop1 = 'scope_prop1_changed';
$scope.prop2 = 'scope_prop2_changed';
$scope.aNumber += 1;
};
}]).directive('d1', function() {
return {
compile: function(tElement, tAttrs) {
console.log('d1-compile:', tAttrs.attr1, tAttrs.attr2, tAttrs.attr3, tAttrs.attr4, tAttrs.attr5, tAttrs.attr6);
return function link(scope, iElement, iAttrs) {
console.log('d1-link:', iAttrs.attr1, iAttrs.attr2, iAttrs.attr3, iAttrs.attr4, iAttrs.attr5, tAttrs.attr6);
console.log("----------------$watch----------------");
scope.$watch(iAttrs.attr1, function(value) {
console.log('d1-watch a1:', value);
});
scope.$watch(iAttrs.attr2, function(value) {
console.log('d1-watch a2:', value);
});
scope.$watch(iAttrs.attr3, function(value) {
console.log('d1-watch a3:', value);
});
scope.$watch(iAttrs.attr4, function(value) {
console.log('d1-watch a4:', value);
});
scope.$watch(iAttrs.attr5, function(value) {
console.log('d1-watch a5:', value);
});
scope.$watch(iAttrs.attr6, function(value) {
console.log('d1-watch a6:', value);
});
};
}
};
});
</script>
</body>
</html>
directive('d1', function() {
return {
compile: function(tElement, tAttrs) {
console.log('d1-compile:', tAttrs.attr1, tAttrs.attr2, tAttrs.attr3, tAttrs.attr4, tAttrs.attr5, tAttrs.attr6);
return function link(scope, iElement, iAttrs) {
console.log('d1-link:', iAttrs.attr1, iAttrs.attr2, iAttrs.attr3, iAttrs.attr4, iAttrs.attr5, tAttrs.attr6);
console.log("----------------$observe----------------");
iAttrs.$observe('attr1', function(value) {
console.log('d1-obsrv a1:', value);
});
iAttrs.$observe('attr2', function(value) {
console.log('d1-obsrv a2:', value);
});
iAttrs.$observe('attr3', function(value) {
console.log('d1-obsrv a3:', value);
});
iAttrs.$observe('attr4', function(value) {
console.log('d1-obsrv a4:', value);
});
iAttrs.$observe('attr5', function(value) {
console.log('d1-obsrv a5:', value);
});
iAttrs.$observe('attr6', function(value) {
console.log('d1-obsrv a6:', value);
});
};
}
};
});
directive('d1', function() {
return {
scope: {
isolate_prop1: '@attr1',
isolate_prop2: '=attr2',
isolate_prop3: '@attr3',
isolate_prop4: '@attr4',
isolate_prop5: '@attr5',
isolate_prop6: '@attr6'
},
compile: function(tElement, tAttrs) {
console.log('d1-compile tAttrs:', tAttrs.attr1, tAttrs.attr2, tAttrs.attr3, tAttrs.attr4, tAttrs.attr5, tAttrs.attr6);
return function link(scope, iElement, iAttrs) {
console.log('d1-link iAttrs:', iAttrs.attr1, iAttrs.attr2, iAttrs.attr3, iAttrs.attr4, iAttrs.attr5, tAttrs.attr6);
console.log('d1-link isolate:', scope.isolate_prop1, scope.isolate_prop2, scope.isolate_prop3, scope.isolate_prop4, scope.isolate_prop5,scope.isolate_prop6);
scope.$watch('isolate_prop1', function(value) {
console.log('d1-watch a1:', value);
});
scope.$watch('isolate_prop2', function(value) {
console.log('d1-watch a2:', value);
});
scope.$watch('isolate_prop3', function(value) {
console.log('d1-watch a3:', value);
});
scope.$watch('isolate_prop4', function(value) {
console.log('d1-watch a4:', value);
});
scope.$watch('isolate_prop5', function(value) {
console.log('d1-watch a5:', value);
});
scope.$watch('isolate_prop6', function(value) {
console.log('d1-watch a6:', value);
});
};
}
};
});