传统的浏览器事件循环 :浏览器本身一直在等待事件,并作出响应。如果你点击一个button或者在input 中输入字符,我们在 JS 中 监听这些事件并设定了回调函数,那么这些事件被触发以后,回调函数就会在浏览器的解释器里执行。执行完毕以后,浏览器就会对相应的 DOM 做出变化。
Angular 的事件循环 :Angular 对传统的循环进行了扩展,生成了一个称为 angular context 的执行环境。
$watch 队列 :
$watch : 你在 controller 中创建数据模型,然后你将它们绑定到 UI 上,同时也会有相应数量的$watch(我觉得队列中的这些$watch 是$watch 这个类的实例对象)被加入到$watch 队列用来监视数据模型的变化。需要注意的是,在 UI 上有多少东西接受了数据绑定,就会生成多少个$watch 插入队列中。这些$watch 是在我们的模板加载完毕的时候,也是就linking 阶段生成的(模板加载之前是compile 阶段)。我们可以用$watch 监视某一数据的变化并设定回调函数来响应事件。
$watch(’ModelName’,function( new Value , oldValue ) { //do sth} );
try it :
$digest 循环:
当浏览器接收到可以被 angular context 处理的事件的时候,$digest 循环就会被触发 。这个循环是有两个小的循环组合起来的。一个处理 evalAsync队列,另一个处理$watch 队列。
$dige st 会依次询问 angular context 中$watch 队列中的每个$watch,这就是所谓的脏值检查(dirty-checking)。‘你的值是多少?你改变过没有?’,直到队列成员都被检查过。一旦有一个值发生过变化,这个循环就会再执行一次,直到所有的$watch 都没有发生过变化。 但是如果连续循环超过10次的话,他将会抛出一个一场,防止无限循环。当$digest 循环结束,DOM 相应地变化。有点蛋疼的是,在这种机制下,即便你在input 输入一个字母,$digest 也会检查整个页面的$watch.
$apply :
既然 angular 是通过$digest 循环来检查页面中的$watch变化情况的,并且我们知道,浏览器只有接收到了 angular context 可以处理的事件时才会启动这个循环。那么是谁决定了哪些事件会进入$digest 循环,哪些不会呢。这就是$apply 。也就是说,$apply 决定了哪些事件会触发脏值检查。很多事件比如ng-click,ng-model,这些元素的事件会进入 angular context。这是 angular 自己帮我们做好的。比如下面的例子
app.directive('clickable', function() { return { restrict: "E", scope: { foo: '=', bar: '=' }, template: '
- { {foo}}
- { {bar}}
这是一个简单的 angular 的 directive 。我们通过 link 函数为它添加了 click 事件。但是当我们点击的时候发现页面并不会进行数据的刷新。
try it :
这是因为我们绑定的click 事件并没有进入 angular context 中。这时候我们就需要通过$apply 来实现。
方法一:修改 link函数:
element.bind('click',function(){
scope.foo++ ;
scope.bar++;
scope.$apply();
});
方法二(推荐):
element.bind('click',function(){
scope.$apply(function(){
scope.foo++ ;
scope.bar++;
})
});
两种方法的区别在于,第一种我们是在 angular context 外面更新的数据。但是一旦处理数据发生错误,angular 就不会知道发生了什么。