美文网首页
AngularJS 的变化检测

AngularJS 的变化检测

作者: lolivialucky | 来源:发表于2017-09-25 15:59 被阅读51次

    AngularJs 1.X的变化检测

    双向绑定

    AngularJs中引入了双向绑定机制

    首先看一下模型和DOM进行双向绑定的代码片段

    // view
    <div>{{eggs}}</div>
    
    // controller
    app.controller('getEggController', [
        '$scope',
        function ($scope) {  
            this._init = function () {
                 $scope.eggs = 'myEgg';
            };
            this._init()
    }]);
    

    在该代码片段中, controller中的模型变量$scope.eggs与模型中{{eggs}}双向绑定,当controller中的$scope.eggs发生变化时,模板中的{{eggs}}同事会变化,相应的模板中的{{eggs}}发生变化时,controller中的$scope.eggs也会变化。这种双向绑定的原理是什么呢? AngularJs框架是怎样实现这种双向绑定机制的呢?

    原理:

    AngularJs会为你再scope模型上设置一个watcher, 它用来在模型数据发生变化时更新view,这里用到的watcher和我们会在angularJs中设置的watcher是一样的。即:

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

    关于$watch:
    http://www.cnblogs.com/yg_zhang/p/4799369.html

    $watch原理

    传入到$watch()中的第二个参数是一个回调函数,该函数在eggs的值发生变化的时候会被调用。那么AngularJS是如何知道什么时候要调用这个回调函数呢?换句话说,AngularJS是如何知晓eggs发生了变化,才调用了对应的回调函数呢?
    AngularJs会周期性的运行一个函数$digest检查scope模型中的数据是否发生了变化,如果模型发生了变化,那么$watch中的回调函数就会被调用。 但是实际上angularJs并没有直接调用$digest(), 而是调用$scope.apply()。$scope.apply()会调用$rootScope.$digest()。因此,一轮$digest循环在$rootScope开始,随后会访问到所有的children scope中的watchers。

    $digest原理

    当$digest()被调用时,angular会遍历当前scope,以及children scope上的所有$watch, 如果watched value发生了变化,则运行回调函数,如果watched value没有变化,则执行下一个watcher。

    当一个$digest循环运行时,watchers会被执行来检查scope中的models是否发生了变化。如果发生了变化,那么相应的listener函数就会被执行。这涉及到一个重要的问题。如果listener函数本身会修改一个scope model呢?
    答案是$digest循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有models发生了变化。这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,$digest循环会持续运行直到model不再发生变化,或者$digest循环的次数达到了10次。因此,尽可能地不要在listener函数中修改model。

    Note:

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

    $digest会在什么情况被触发呢? 一般当发生了一下Angular Context的事件的时候会触发$digest, 比如:

    1. DOM Events(eg. ng-click etc)
    2. Ajax的回调 (eg. $http etc)
    3. Timer with callbacks(eg. $timeout etc)
    4. 直接调用 $apply, $digest.

    而对于普通的非angular上下文的异步事件,比如onclick, setTimeout等则不会触发$digest.因此对于这些非angular上下文的异步事件,要想进行模型的更新,则需要手动调用$apply手动进行模型的更新.

    image.png
    $scop.apply()

    $scope.$apply()会自动地调用$rootScope.$digest()。$apply()方法有两种形式。第一种会接受一个function作为参数,执行该function并且触发一轮$digest循环。第二种会不接受任何参数,只是触发一轮$digest循环。而在手动使用 的过程中,推荐使用第一种方式,讲一个函数包裹在$scope.$apply()中,因为这种方法会自动的添加try....catch语句进行错误处理。
    比如,对于以下语句:

    $scope.$apply(doSomething)

    本质上等同于:

    doSomething();
    $scope.digest();

    下面来看一个例子

    <!DOCTYPE html>
    <html lang="en" ng-app="myApp">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <script src="https://code.angularjs.org/1.2.9/angular.min.js"></script>
    <script>
        angular.module('myApp', [])
            .run(function($rootScope){
                var $scope = $rootScope;
                $scope.$watch('enabled', function(val) {
                    console.log('You are now: ' + (val ? 'enabled' : 'disabled'));
                });
                $scope.enabled = true;
                $scope.enabled = false;
                $scope.enabled = 1;
            })
    </script>
    <body>
        <p ng-if="enabled">I am here because I'm enabled</p>
        <button ng-click="enabled=!enabled">Click Me!</button>
    </body>
    </html>
    

    运行该代码,可以看到浏览器里面console打出的结果是 "You are now: enabled"。
    但是你也许会疑惑,对于scope上的模型enabled明明进行了三次赋值,为什么只打出了一条log?
    这是因为当$scope.enabled被第一次赋值初始化时,digest循环只会执行一次,若想要log语句三次都被执行,则要手动去调用$scope.apply()方法。 代码如下:

    <!DOCTYPE html>
    <html lang="en" ng-app="myApp">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <script src="https://code.angularjs.org/1.2.9/angular.min.js"></script>
    <script>
        angular.module('myApp', [])
            .run(function($rootScope){
                var $scope = $rootScope;
                $scope.$watch('enabled', function(val) {
                    console.log('You are now: ' + (val ? 'enabled' : 'disabled'));
                });
                $scope.$apply(function() {
                    $scope.enabled = true;
                });
                $scope.$apply(function() {
                    $scope.enabled = false;
                });
                $scope.$apply(function() {
                    $scope.enabled = 1;
                });
            })
    </script>
    <body>
        <p ng-if="enabled">I am here because I'm enabled</p>
        <button ng-click="enabled=!enabled">Click Me!</button>
    </body>
    </html>
    
    这也就说明了,在controller里进行初始赋值时如果不手动调用$scope.apply(),对于$scope上的模型的赋值操作只有第一次才会生效。
    补充: $apply 和 $digest的一些区别:
    image.png

    参考文章
    https://www.thinkful.com/projects/understanding-the-digest-cycle-528/
    http://blog.csdn.net/dm_vincent/article/details/38705099

    相关文章

      网友评论

          本文标题:AngularJS 的变化检测

          本文链接:https://www.haomeiwen.com/subject/cdnxextx.html