深入理解$watch ,$apply 和 $digest --- 理解数据绑定过程——续
Angular什么时候不会自动为我们$apply
呢?
这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply
,事件没有进入angular context
,$digest
循环永远没有执行。
我们来看一个有趣的例子:
假设我们有下面这个directive和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++;
});
})
因此,如果你想使用一个jQuery插件,并且要执行$digest
循环来更新你的DOM的话,要确保你调用了$apply
。
使用$watch
来监视你自己的东西
你已经知道了我们设置的任何绑定都有一个它自己的$watch
,当需要时更新DOM,但是我们如果要自定义自己的watches呢?简单
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++;
});
});
index.html
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
watch的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。
总结
好吧,我希望你们已经学会了在Angular中数据绑定是如何工作的。我猜想你的第一印象是dirty-checking
很慢,好吧,其实是不对的。它像闪电般快。但是,是的,如果你在一个模版里有2000-3000个watch
,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了!