AngularJS学习之$digest循环和$apply

来源:互联网 发布:非全日制法律硕士 知乎 编辑:程序博客网 时间:2024/05/08 06:43

1、$watch列表

    对于所有绑定给同一$scope对象的UI元素,只会添加一个$watch到$watch列表中。
    这些$watch列表会在$digest循环中通过一个叫做“脏值检查”的程序解析。


2、脏值检查

    检查值是否发生了变化,而整个应用还没同步该变化

    Anguar遍历完整个$watch列表,只要有任何值发生变化,应用将会退回到$watch循环中,直到检测到不再有任何变化。


3、$watch

$scope对象上的$watch方法会给Angular事件循环内的每个$digest调用装配一个脏值检查。如果在表达式上检测到变化, Angular总是会返回$digest循环。

$watch函数本身接受两个必要参数和一个可选的参数:


watchExpression

watchExpression可以是一个作用域对象的属性,或者是一个函数。在$digest循环中的每个$digest调用都会涉及它。

如果watchExpression是一个字符串, Angular会在$scope上下文中对它求值。如果它是一个函数,那么Angular会认为它会返回应该被监控的值。


listener/callback

作为回调的监听器函数,它只会在watchExpression的当前值与先前值不相等(除了首次运行初始化期间)时调用。


objectEquality(可选)

objectEquality是一个进行比较的布尔值,用来告诉Angular是否检查严格相等。

$watch函数会给监听器返回一个注销函数,我们可以调用这个注销函数来取消Angular对当前值的监控。

//...

var unregisterWatch =

    $scope.$watch('newUser.email', function(newVal, oldVal) {

        if (newVal === oldVal) return; // 初始化

});

// ...

// 稍后,可以通过调用这个注销函数来注销这个监控器

unregisterWatch();

注:永远不要在控制器中使用$watch,因为它会使控制器难以测试。将这些监控移到相应的服务中。


4、$watchCollection

Angular还允许我们为对象的属性或者数组的元素设置浅监控,然后只要属性发生变化就触发监听器回调。

使用$watchCollection还可以检测对象或数组何时发生了变化,以便确定对象或数组中的条目是何时添加、移除或者移动的。 $watchConllection的行为与$digest循环中标准的$watch的行为一样,我们甚至可以把它当作标准的$watch。

$watchCollectiion()函数接受2个参数。


obj(字符串/函数)

这个对象就是一个要监控的对象。如果传入一个字符串,它将被当作Angular表达式求值。

如果传入的是一个函数,将在当前作用域中被调用,并且会返回要监控的值。


listener(函数)

这个回调函数会在集合发生变化时触发。类似于$watch函数,这个函数会被来自$watch的新集合触发调用,而原来的集合(先前集合的副本)以及所在的作用域也随之生效。

$watchConllection()函数也返回一个注销函数。调用这个注销函数时,也会取消集合上的$watch。

$scope.$watchCollection('names',  function(newNames, oldNames, scope) {

        // names集合已经发生了变化

});


5、页面中的$digest循环

例如ng-model="name",通过ng-model指令在视图中绑定了一个name, Angular会设置一个隐式的监控器,将这个输入字段的值绑定为当前的$scope对象。当这个值变化时,Angular上下文就会生效并开始遍历$$watchers($watch列表),这个监控函数就会在$scope.name绑定上执行。在我们退出$digest循环之前,这一行为会触发在该值(由ng-model绑定)上运行的验证和格式化操作。

ng-click为DOM元素绑定了浏览器原生的click事件。当这个DOM元素收到点击事件时,ng-click指令会调用 $scope.$apply(),同时进入$digest循环。

注:由于在digest循环中值发生了变化, Angular需要再次运行这一循环以确定它没有改变作用域对象上的其他值。


6、$evalAsync列表

$evalAsync()方法是一种在当前作用域上调度表达式在未来某个时刻运行的方式。$digest循环运行的第二个操作是执行 $$asyncQueue。可以使用$evalAsync()方法访问这个工作队列。

$digest循环期间,贯穿脏值检查生命周期的每个循环之间的队列都是空的,这意味着使用$evalAsync来调用任何函数都会发生两件事情。

(1) 函数会在这个方法被调用的某个时刻之后执行。

(2) 表达式求值之后至少会执行一次$digest循环。


$evalAsync()方法接受一个唯一参数:

expression(字符串/函数)

这个表达式便是我们想要在当前作用域上执行的东西。如果传入一个字符串, Angular将会在当前作用域上使用$eval求值该表达式。如果传入的是一个函数, Angular将会使用传递给这个函数的scope对象执行函数求值。

$scope.$evalAsync('attribute',function(scope) {

    scope.foo = "Executed"

});


使用$evalAsync时要注意的一些细节:

如果指令直接调用$evalAsync(),它会在Angular操作DOM之后、浏览器渲染之前运行。

如果控制器调用$evalAsync(),它也会在Angular操作DOM之后、浏览器渲染之前运行 (永远不要使用$evalAsync()来约定事件的顺序)。

无论何时,在Angular中,只要你想要在一个行为的执行上下文外部执行另一个行为,就应该使用$evalAsync()函数

还可以使用它替代setTimeout()函数,但是它可能在浏览器重新渲染视图之后导致屏幕闪烁。


7、$apply

$apply()函数可以从Angular框架的外部让表达式在Angular上下文内部执行。例如,假设你实现了一个setTimeout()或者使用第三方库并且想让事件运行在Angular上下文内部时,就必须使用$apply()。

$apply()函数接受一个可选的参数:

expression(字符串/函数)

这个表达式可选地接受一个字符串或函数,并且是在当前作用域内执行。

如果传入一个字符串, $apply()首先会在这个字符串上调用$eval(),以强制Angular在局部作用域上下文中使用$eval()运行字符串表达式。

如果传入一个函数,这个函数将会在所传入的函数作用域上执行。

$exceptionHandler服务会捕获和处理$eval()方法抛出的所有异常。最后, $apply()方法还会直接调用$digest循环。

// 使用要eval的字符串调用$apply示例
$scope.$apply('message = "Hello World"');
// 使用函数的方式并给函数传入一个作用域
$scope.apply(function(scope) {
// 然后在函数中使用传入作用域
scope.message = "Hello World";
});
// 使用函数时忽略作用域
$scope.$apply(function() {
$scope.message = "Hello World";
});
// 或者通过在操作的尾部调用$apply()以强制运行$digest循环
$scope.apply();

简而言之,使用$scope.$apply()时可以从外部进入上下文。

如果在事件被触发时调用$apply(),就会使用Angular事件循环来运行它。如果没有调用$apply(),就不会在事件循环内执行这个函数,而它会运行在Angular上下文外部。


8、调用$apply

通常可以依赖于Angular提供的可用于视图中的任意指令来调用$apply()。所有ng-[event]指令(比如ng-click、 ng-keypress)都会调用$apply()。

此外还可以依赖于一系列Angular内置的服务来调用$digest()。比如$http服务会在XHR请求完成并触发更新返回值(promise)之后调用$apply()。

无论何时我们手动处理事件,使用第三方框架(比如jQuery、 Facebook API) ,或者调用setTimeout(),都可以使用$apply()函数让Angular返回$digest循环。

当我们将jQuery和Angular集成在一起时(这通常被视为一个肮脏的行为),就需要使用$apply(),因为Angular不会察觉到执行在Angular上下文外部的事件。例如,在使用jQuery插件时(比如datepicker),就需要使用$apply()将来自jQuery的值传递到Angular应用中。

1 0
原创粉丝点击