zoukankan      html  css  js  c++  java
  • 理解Angular中的$apply()以及$digest()

    $apply()$digest()AngularJS中是两个核心概念。可是有时候它们又让人困惑。

    而为了了解AngularJS的工作方式,首先须要了解$apply()$digest()是怎样工作的。这篇文章旨在解释$apply()$digest()是什么,以及在日常的编码中怎样应用它们。

     

    探索$apply()$digest()

    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循环。

     

    眼下为止还不错!

    可是,有一个小问题。在上面的样例中,AngularJS并不直接调用$digest()。而是调用$scope.$apply()。后者会调用$rootScope.$digest()。因此。一轮$digest循环在$rootScope開始。随后会訪问到全部的children scope中的watchers

     

    如今,如果你将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的更改)AngularJSbuilt-in指令就是这样做的,所以不论什么的model变更都会被反映到view中。可是,假设你在AngularJS上下文之外的不论什么地方改动了model,那么你就须要通过手动调用$apply()来通知AngularJS。这就像告诉AngularJS。你改动了一些models。希望AngularJS帮你触发watchers来做出正确的响应。

     

    比方,假设你使用了JavaScript中的setTimeout()来更新一个scope model,那么AngularJS就没有办法知道你更改了什么。这样的情况下。调用$apply()就是你的责任了。通过调用它来触发一轮$digest循环。类似地,假设你有一个指令用来设置一个DOM事件listener而且在该listener中改动了一些models。那么你也须要通过手动调用$apply()来确保变更会被正确的反映到view中。

     

    让我们来看一个样例。增加你有一个页面。一旦该页面载入完成了,你希望在两秒钟之后显示一条信息。你的实现可能是以下这个样子的:

     

    HTML:

    <body ng-app="myApp">
      <div ng-controller="MessageController">
        Delayed Message: {{message}}
      </div>  
    </body>


    JavaScript:
    /* What happens without an $apply() */
        
        angular.module('myApp',[]).controller('MessageController', function($scope) {
        
          $scope.getMessage = function() {
            setTimeout(function() {
              $scope.message = 'Fetched after 3 seconds';
              console.log('message:'+$scope.message);
            }, 2000);
          }
          
          $scope.getMessage();
        
        });

     

    通过执行这个样例。你会看到过了两秒钟之后,控制台确实会显示出已经更新的model。然而,view并没有更新。原因或许你已经知道了,就是我们忘了调用$apply()方法。因此,我们须要改动getMessage(),例如以下所看到的:

     

    /* What happens with $apply */ 
    angular.module('myApp',[]).controller('MessageController', function($scope) {
        
          $scope.getMessage = function() {
            setTimeout(function() {
              $scope.$apply(function() {
                //wrapped this within $apply
                $scope.message = 'Fetched after 3 seconds'; 
                console.log('message:' + $scope.message);
              });
            }, 2000);
          }
          
          $scope.getMessage();
        
        });

     

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

     

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

     

    并且,注意在以上的代码中你也能够在改动了model之后手动调用没有參数的$apply()。就像以下这样:

     

    $scope.getMessage = function() {
      setTimeout(function() {
        $scope.message = 'Fetched after two seconds';
        console.log('message:' + $scope.message);
        $scope.$apply(); //this triggers a $digest
      }, 2000);
    };

     

    以上的代码使用了$apply()的另外一种形式,也就是没有參数的形式。

    须要记住的是你总是应该使用接受一个function作为參数的$apply()方法。

    这是由于当你传入一个function$apply()中的时候。这个function会被包装到一个trycatch块中。所以一旦有异常发生,该异常会被$exceptionHandler service处理。

     

    $digest循环会执行多少次?

    当一个$digest循环运行时,watchers会被运行来检查scope中的models是否发生了变化。假设发生了变化,那么对应的listener函数就会被运行。这涉及到一个重要的问题。

    假设listener函数本身会改动一个scope model呢?AngularJS会怎么处理这样的情况?

     

    答案是$digest循环不会仅仅执行一次。

    在当前的一次循环结束后,它会再执行一次循环用来检查是否有models发生了变化。

    这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,$digest循环会持续执行直到model不再发生变化。或者$digest循环的次数达到了10次。因此。尽可能地不要在listener函数中改动model

     

    Note: $digest循环最少也会执行两次,即使在listener函数中并没有改变不论什么model。正如上面讨论的那样,它会多执行一次来确保models没有变化。

     

    结语

    我希望这篇文章解释清楚了$apply$digest

    须要记住的最重要的是AngularJS能否检測到你对于model的改动。假设它不能检測到,那么你就须要手动地调用$apply()


    原文地址

    http://www.sitepoint.com/understanding-angulars-apply-digest/


     

     

     

     

     

  • 相关阅读:
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    129. Sum Root to Leaf Numbers
    117. Populating Next Right Pointers in Each Node II
  • 原文地址:https://www.cnblogs.com/lytwajue/p/7293786.html
Copyright © 2011-2022 走看看