angularjs基础—$scope.$apply的应用

来源:互联网 发布:linux下怎么看java目录 编辑:程序博客网 时间:2024/05/16 11:38

$apply() $digest() AngularJS 中是两个核心概念

AngularJS 提供了一个非常酷的特性叫做双向数据绑定(Two-way Data Binding) ,这个特性大大简化了我们的代码编写方式。

数据绑定意味着当 View 中有任何数据发生了变化,那么这个变化也会自动地反馈到 scope 的数据上,也即意味着 scope 模型会自动地更新。

类似地,当 scope 模型发生变化时, view 中的数据也会更新到最新的值。

那么 AngularJS 是如何做到这一点的呢?当你写下表达式如 {{ aModel }} 时, AngularJS 在幕后会为你在 scope 模型上设置一个 watcher ,它用来在数据发生变化的时候更新 view


这里的 watcher 和你会在 AngularJS 中设置的 watcher 是一样的:

$scope.$watch('aModel', function(newValue, oldValue) {  //update the DOM with newValue});

传入到 $watch() 中的第二个参数是一个回调函数,该函数在 aModel 的值发生变化的时候会被调用。当aModel 发生变化的时候,这个回调函数会被调用来更新 view ,,但是,还存在一个很重要的问题! AngularJS是如何知道什么时候要调用这个回调函数呢?即, AngularJS是如何知晓 aModel 发生了变化,才调用了对应的回调函数呢?它会周期性的运行一个函数来检查 scope 模型中的数据是否发生了变化吗?好吧,这就是 $digest 循环的用武之地了。


$digest 循环中, watchers 会被触发。当一个watcher 被触发时, AngularJS 会检测 scope 模型,如何它发生了变化那么关联到该 watcher 的回调函数就会被调用。那么,下一个问题就是$digest 循环是在什么时候以各种方式开始的?

在调用了 $scope.$digest() 后, $digest 循环就开始了。假设你在一个ng-click 指令对应的 handler 函数中更改了 scope 中的一条数据,此时 AngularJS 会自动地通过调用$digest() 来触发一轮 $digest 循环。当 $digest 循环开始后,它会触发每个 watcher 。这些watchers 会检查 scope 中的当前 model 值是否和上一次计算得到的 model 值不同。如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是view 中的表达式内容 ( 译注:诸如 {{ aModel }}) 会被更新。除了 ng-click 指令,还有一些其它的built-in 指令以及服务来让你更改 models( 比如 ng-model $timeout ) 和自动触发一次 $digest 循环。

现在,假设你将 ng-click 指令关联到了一个 button 上,并传入了一个function 名到 ng-click 上。当该 button 被点击时, AngularJS 会将此 function 包装到一个 wrapping function中,然后传入到 $scope.$apply() 。因此,你的 function 会正常被执行,修改models( 如果需要的话 ) ,此时一轮 $digest 循环也会被触发,用来确保 view 也会被更新。

Note: $scope.$apply() 会自动地调用$rootScope.$digest() $apply() 方法有两种形式。第一种会接受一个 function作为参数,执行该 function 并且触发一轮 $digest 循环。第二种会不接受任何参数,只是触发一轮$digest 循环。我们马上会看到为什么第一种形式更好。

什么时候手动调用 $apply() 方法?

如果 AngularJS 总是将我们的代码 wrap 到一个 function 中并传入 $apply(),以此来开始一轮 $digest 循环,那么什么时候才需要我们手动地调用 $apply() 方法呢?实际上, AngularJS 对此有着非常明确的要求,就是它只负责对发生于AngularJS 上下文环境中的变更会做出自动地响应 ( 即,在 $apply() 方法中发生的对于 models 的更改 ) AngularJS built-in 指令就是这样做的,所以任何的model 变更都会被反映到 view 中。但是,如果你在 AngularJS上下文之外的任何地方修改了 model ,那么你就需要通过手动调用 $apply() 来通知AngularJS 。这就像告诉 AngularJS ,你修改了一些 models,希望 AngularJS 帮你触发 watchers 来做出正确的响应。

我们到底什么时候需要去调用apply()方法呢?情况非常少,实际上几乎我们所有的代码都包在scope.apply()里面,像ng−click,controller的初始化,http的回调函数等。在这些情况下,我们不需要自己调用,实际上我们也不能自己调用,否则在apply()方法里面再调用apply()方法会抛出错误。如果我们需要在一个新的执行序列中运行代码时才真正需要用到它,而且当且仅当这个新的执行序列不是被angular JS的库的方法创建的,这个时候我们需要将代码用scope.apply()包起来。下面用一个例子解释:

<!DOCTYPE html><html lang="en" ng-app="myapp"><head>    <meta charset="UTF-8">    <title>$apply</title>    <script src="js/angular.js"></script></head><body><div ng-controller="ServiceController">{{message}}</div><script>    var app = angular.module('myapp', []);    app.controller('ServiceController', function($scope) {        $scope.message ="Waiting 2000ms for update";        setTimeout(function () {            $scope.message ="Timeout called!";        }, 2000);    });</script></body></html>

上面的代码执行后页面上会显示:Waiting 2000ms for update。显然数据的更新没有被angular JS觉察到。
     接下来,我们将Javascript的代码稍作修改,用scope.apply()包起来。

<!DOCTYPE html><html lang="en" ng-app="myapp"><head>    <meta charset="UTF-8">    <title>$apply</title>    <script src="js/angular.js"></script></head><body><div ng-controller="ServiceController">{{message}}</div><script>    var app = angular.module('myapp', []);    app.controller('ServiceController', function($scope) {        $scope.message ="Waiting 2000ms for update";        setTimeout(function () {            $scope.$apply(function () {                $scope.message ="Timeout called!";            });        }, 2000);    });</script></body></html>

   这次与之前不同的是,页面上先会显示:Waiting 2000ms for update,等待2秒后内容会被更改为:Timeout called! 。显然数据的更新被angular JS觉察到了。
     NOTE:我们不应该这样做,而是用angular JS提供的timeout方法,这样它就会被自动用apply方法包起来了

如果你运行了上面的例子,你会看到 view 在两秒钟之后也会更新。唯一的变化是我们的代码现在被 wrapped 到了 $scope.$apply() 中,它会自动触发$rootScope.$digest() ,从而让 watchers 被触发用以更新 view

Note: 顺便提一下,你应该使用$timeout service 来代替 setTimeout() ,因为前者会帮你调用 $apply(),让你不需要手动地调用它。

总结:

需要记住的最重要的是 AngularJS 是否能检测到你对于 model 的修改。如果它不能检测到,那么你就需要手动地调用$apply()


0 0
原创粉丝点击