浏览器事件发生时,会在浏览器的上下文window中执行,而angular有自己的上下文angular content,angular 事件在自己的上下文angular content中执行。
$watch
$watch组成了双向绑定的一边,在指定的表达式改变时调用,每个作用域都维护了一个监视器列表,被称为$scope.$$watchers。每次你绑定一些东西到你的UI上时你就会往$watch队列里插入一条$watch。
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul> app.controller('MainCtrl', function($scope) {
$scope.people = [
{name: 'a', age: 21},
{name: 'b', age: 25},
{name: 'c', age: 22}
];
});
这个例子生成了多少个$watch呢?ng-repeat一个,每个person两个,一共(2*3)+1=7个。
除了在视图上绑定数据创建一个$watch,还可以自定义$watch,但是有几点需要注意。
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body> 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); //第三个参数可选,如果设为true,则会比较对象的值,否则比较引用
});
当controller执行到这个$watch
时,它会立即执行一次,所以我们加入了判断来防止第一个初始化的调用。其次是第三个参数,如果省略或者为false,则以上例子输入框的值改变之后,update的值不会改变,因为比较的user的引用。
每一个绑定到了UI上的数据都会生成一个$watch,那这些
$watch
是什么时候生成的呢?当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段),Angular解释器会寻找每个directive,然后生成每个需要的$watch。
$digest
var dirty = true;
var iterations = 0;
while (dirty && iterations++ < TIMES_TO_LOOP) {
dirty = false;
for (var i = 0; i < scope.watchers.length(); i++) {
var currentValue = scope.watchers[i].get();
if (currentValue != scope.watchers[i].oldValue) {
dirty = ture; //如果有数据改变了,则又$digest一次,直到没有数据改变
scope.watchers[i].callback(currentValue, scope.watchers[i].oldValue);
scope.watchers[i].oldValue = currentValue;
}
}
}
angular将TIMES_TO_LOOP(TTL)默认设为10,即$digest最多循环10次,超出后将抛出一个错误,防止无限循环,我们可以通过$rootScope服务和digestTtl函数改变这个值。
var app = angular.module('MyApp', [], function ($rootScopeProvider) {
$rootScopeProvider.digestTtl(15); //将TTL改为15
});
angular的内置指令会默认调用$digest,还有一点需要注意的是,UI中的所有指令都将创建至少一个监视器,所以angular的性能问题几乎总是因为以不合理的方式使用ngRepeat指令所引起的。如以上第一段代码,如果person对象达到10000个以上就会严重影响性能,实际上angular建议单个页面中不要使用超过2000个监视器。
angular.module('app', function ($scope) {
setTimeout(function () {
$scope.arr = [1];
$scope.$digest(); //setTimeout中手动调用$digest
});
});
$apply
function $apply(expr) {
try {
return$eval(expr);
} catch(e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
事件发生时是谁在决定事件是在window中执行还是angular content执行?答案是$apply(),例如内置的ngClick指令会默认调用$apply。那么什么时候需要我们手动调用$apply呢?和$digest一样,在angular里面执行angular框架之外的表达式,比如说:浏览器DOM事件、setTimeout、XHR或其他第三方的库时,这时就要求我们手动调用$apply()。
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply(); //我们是在angular context的外面更新的数据,
//如果有发生错误,Angular永远不知道
}); element.bind('click', function() {
scope.$apply(function() { //把它封装进$apply里面,错误会传到angular里
scope.foo++; //例如一个服务器获取数据失败
scope.bar++;
});
})