Angular之双向数据绑定(下)

本篇详细介绍:1.angular时如何通过脏检查来实现对$scope对象上变量的双向绑定的。2.实现angular双向绑定的三个重要方法:$digest(),$apply(),$watch().

angular不像Ember.js,通过动态设置setter函数和getter函数来实现双向绑定,脏检查允许angular监听可能存在可能不存在的变量。

$scope.$watch语法糖:$scope.$watch(watchExp,Listener,objectEquality);

监听一个变量何时变化,需要调用$scope.$watch函数,这个函数接受三个参数:需要检测的值或者表达式(watchExp),监听函数,值变化时执行(Listener匿名函数),是否开启值检测,为 true时会检测对象或者数组的内部变更(即选择以===的方式比较还是angular.equals的方式)。举个例子:

1 $scope.name = ‘Ryan‘;
2
3 $scope.$watch( function( ) {
4     return $scope.name;
5 }, function( newValue, oldValue ) {
6     console.log(‘$scope.name was updated!‘);
7 } );

angular会在$scope对象上注册你的监听函数Listener,你可以注意到会有日志输出“$scope.name was updated!”,因为$scope.name由先前的undefined更新为‘Ryan’。当然,watcher也可以是一个字符串,效果和上面例子中的匿名函数一样,在angular源码中,

1 if(typeof watchExp == ‘string‘ &&get.constant){
2 var originalFn = watcher.fn;
3   watcher.fn = function(newVal, oldVal, scope) {
4     originalFn.call(this, newVal, oldVal, scope);
5     arrayRemove(array, watcher);
6   };
7 }

上面这段代码将watchExp设置为一个函数,这个函数会调用带有给定变量名的listener函数。

下面举个应用实例,以插值{{post.title}}为例,当angular在compile编译阶段遇到这个语法元素时,内部处理逻辑如下:

walkers.expression = function( ast ){
  var node = document.createTextNode("");
  this.$watch(ast, function(newval){
    dom.text(node, "" + (newval == null? "": "" + newval) );
  })
  return node;
}

这段代码很好理解,就是当遇到插值时,会新建一个textNode,并把值写入到该nodeContent中.那么angular怎么判断这个节点值改变或者说新增了一个节点?

这里就不得不提到$digest函数。首先,通过$watch接口,会产生一个监听队列$$watchers。$scope对象下的的$$watchers对象下拥有你定义的所有的watchers。如果你进入到$$watchers内部,会发现它这样的一个数组。

$$watchers = [
    {
        eq: false, // whether or not we are checking for objectEquality  是否需要判断对象级别的相等
        fn: function( newValue, oldValue ) {}, // this is the listener function we‘ve provided  这是我们提供的监听器函数
        last: ‘Ryan‘, // the last known value for the variable$nbsp;$nbsp;变量的最新值
        exp: function(){}, // this is the watchExp function we provided$nbsp;$nbsp;我们提供的watchExp函数
        get: function(){} // Angular‘s compiled watchExp function   angualr编译过的watchExp函数
    }
];

$watch函数会返回一个deregisterWatch function,这意味着如果我们使用scope.$watch对一个变量进行监视,那么也可以通过调用deregisterWatch这个函数来停止监听。



我是萌萌嗒分割线

在angularJs中,当一个controller/directive/etc在运行时,angular内部会先运行$scope.$apply()函数,这个函数接受一个参数,参数为一个函数fn,这个函数就是用来执行fn函数的,执行完fn后才会在$rootScope作用域中运行$scope.$digest这个函数。angular源码中时这样描述$apply这个函数的。

      $apply: function(expr) {
        try {
          beginPhase(‘$apply‘);
          try {
            return this.$eval(expr);
          } finally {
            clearPhase();
          }
        } catch (e) {
          $exceptionHandler(e);
        } finally {
          try {
            $rootScope.$digest();
          } catch (e) {
            $exceptionHandler(e);
            throw e;
          }
        }
      }

上面的expr这个参数实际上是一个函数,这个函数是你或者angular在调scope.$apply这个函数时传入的。但是大多数时候你可能都不会去使用这个函数,用的时候记得给他传入一个function参数。

ok,说了这么多,让我们看看angular事怎么使用$scope.$apply的,下面以ng-keydown这个指令来举例,为了注册这个指令,且看源码是如何申明的:

var ngDirectives = {};
forEach(‘click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste‘.split(‘,‘),function(){
    var directiveName = directiveNormalize(‘ng-‘ + name);
    ngEventDirectives[directiveName] = [‘$parse‘, function($parse) {
      return {
        compile: function($element, attr) {
          var fn = $parse(attr[directiveName]);
          return function ngEventHandler(scope, element) {
            element.on(lowercase(name), function(event) {
              scope.$apply(function() {
                fn(scope, {$event:event});
              });
            });
          };
        }
      };
    }];
});

上面这段代码遍历了各种不同的可能被触发的event类型,并创建一个叫ng-[EventNameHere](中括号中为事件名),在这个directive的的compile函数中,它在元素上注册了一个事件处理器,事件和对应的directive名字一一对应,比如,cilck事件和ng-click指令对应。当click事件被触发(或者说ng-click指令被触发),angular会执行scope.$apply,执行$apply中的参数(参数为function)。

上面的代码只是改变了和元素(elment)相关联的$scope中的值。这只是单向绑定。这也是这个指令叫做ng-keydown的原因,只有在keydown事件被触发时,能够给与我们一个新值。不是说angular实现了双向数据绑定吗?!

下面看一看ng-model这个directive,当你在使用ng-model时,你可以使用双向数据绑定 – 这正是我们想要的。AngularJS使用$scope.$watch(视图到模型)以及$scope.$apply(模型到视图)来实现这个功能。

ng-model会把事件处理指令(例如keydown)绑定到我们运用的输入元素上 – 这就是$scope.$apply被调用的地方!而$scope.$watch是在指令的控制器中被调用的。你可以在下面代码中看到这一点:

$scope.$watch(function ngModelWatch() {
    //获取ngModelController中的$scope对象,即数据模型;  var value = ngModelGet($scope);
    //如果作用域模型值和ngModel值没有同步;$modelValue为模型绑定的值,value为数据模型的真实值,$viewValue为视图中展示的值。ngModel.ngMOdelController.$gormatters属性是为了格式化或者转化ngModel控制器中数据模型,$render函数在$modelValue和$viewValue不相等时,需要调用。
    if (ctrl.$modelValue !== value) {

        var formatters = ctrl.$formatters,
            idx = formatters.length;

        ctrl.$modelValue = value;
        while(idx--) {
            value = formatters[idx](value);
        }

        if (ctrl.$viewValue !== value) {
            ctrl.$viewValue = value;
            ctrl.$render();
        }
    }

    return value;
});

如果你在调用$scope.$watch时只为它传递了一个参数,无论作用域中的什么东西发生了变化,这个函数都会被调用。在ng-model中,这个函数被用来检查模型和视图有没有同步,如果没有同步,它将会使用新值来更新模型数据。这个函数会返回一个新值,当它在$digest函数中运行时,我们就会知道这个值是什么!

那么,为什么有时候我们的监听器并没有被触发或者说不起作用?

正如前面所提到的,AngularJS将会在每一个指令的控制器函数中运行$scope.$apply。如果我们查看$scope.$apply函数的代码,我们会发现它只会在控制器函数已经开始被调用之后才会运行$digest函数 – 这意味着如果我们马上停止监听,$scope.$watch函数甚至都不会被调用!因此当$scope.$apply运行的时候,$digest也会运行,它将会循环遍历$$watchers,只要发现watchExp和最新的值不相等,变化触发事件监听器。在AngularJS中,只要一个模型的值可能发生变化,$scope.$apply就会运行。这就是为什么当你在AngularJS之外更新$scope时,例如在一个setTimeout函数中,你需要手动去运行$scope.$apply():这能够让AngularJS意识到它的作用域发生了变化。

但是digest过程究竟是怎样运行的呢?(下面仔细探索源码中$digest函数执行流程,可以不看。。。)

1.首先,标记dirty = false ;

2.遍历当前作用域中的监听对象(current.$$watchers),并且通过判断当前监听对象数组中值watch.get(current)和老值watch.last是否相等:如果不相等,将标记dirty设置成true,将上一个监听对象lastDirtyWatch赋值为当前监听对象,并且将监听对象的老值watch.last赋值为新值,最后,调用watch对象绑定的Listener函数wantch.fn。

traverseScopesLoop:
          do { // "traverse the scopes" loop
            if ((watchers = current.$$watchers)) {
              // process our watches
              length = watchers.length;
              while (length--) {
                try {
                  watch = watchers[length];
                  // Most common watches are on primitives, in which case we can short
                  // circuit it with === operator, only when === fails do we use .equals
                  if (watch) {
                    if ((value = watch.get(current)) !== (last = watch.last) &&
                        !(watch.eq
                            ? equals(value, last)
                            : (typeof value === ‘number‘ && typeof last === ‘number‘
                               && isNaN(value) && isNaN(last)))) {
                      dirty = true;
                      lastDirtyWatch = watch;
                      watch.last = watch.eq ? copy(value, null) : value;
                      watch.fn(value, ((last === initWatchVal) ? value : last), current);
                      if (ttl < 5) {
                        logIdx = 4 - ttl;
                        if (!watchLog[logIdx]) watchLog[logIdx] = [];
                        watchLog[logIdx].push({
                          msg: isFunction(watch.exp) ? ‘fn: ‘ + (watch.exp.name || watch.exp.toString()) : watch.exp,
                          newVal: value,
                          oldVal: last
                        });
                      }
                    } else if (watch === lastDirtyWatch) {
                      // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
                      // have already been tested.
                      dirty = false;
                      break traverseScopesLoop;
                    }
                  }
                } catch (e) {
                  $exceptionHandler(e);
                }
              }
            }

3.进入下一个watch的检查,遍历检查一轮后,如果dirty===true,我们重新进入步骤1. 否则进入步骤4.

4.完成脏检查。

最后,表达一下个人对这块的看法。作为初学的话,不需要去理解他具体事如何实现数据双向绑定的。只要知道他通过脏检查来实现的,需要主动去触发一些事件才能产生。要想进入$digest cycle:

要满足:

  • DOM事件,譬如用户输入文本,点击按钮等。(ng-click)
  • XHR响应事件 ($http)
  • 浏览器Location变更事件 ($location)
  • Timer事件($timeout, $interval)
  • 执行$digest()或$apply()

到此为止,说了很多不需要了解的东西,下面的篇章不会这么废话了。

时间: 2025-01-12 04:08:13

Angular之双向数据绑定(下)的相关文章

Angular JS - 3 - Angular JS 双向数据绑定

一 .数据绑定 1. 数据绑定: 数据从一个地方A转移(传递)到另一个地方B, 而且这个操作由框架来完成2. 双向数据绑定: 数据可以从View(视图层)流向Model(模型,也就是数据), 也可以从Model流向View 视图(View): 也就是我们的页面(主要是Andular指令和表达式) 模型(Model) : 作用域对象(当前为$rootScope), 它可以包含一些属性或方法 当改变View中的数据, Model对象的对应属性也会随之改变: ng-model指令 数据从View==>

Angular之双向数据绑定

---恢复内容开始--- angular最初进入前端开发人员视野的时候,给人以不可磨灭的印象之一就是它的双向数据绑定的实现.本篇章会先介绍如何使用此功能,然后在深入解释它的双向绑定的机制是如何实现的. angular中的data-binding指的是模型models和视图views之间的自动同步.angular实现双向绑定后,会让你觉得数据模型是页面中数据唯一的真实来源.当model改变后,视图反映改变,反之亦然.通俗的说,所谓的双向数据绑定,无非就是从界面的操作能实时反映到数据,数据的变更能实

angular.js双向数据绑定实现动画特效

一.HTML 1.引入必要的js文件,这个不多说了(注意由于之后要使用angular提供的动画效果和路由效果,所以要引入angular-animate.js和angular-route.js两个文件) 2.body内加入以下代码:  <div class="page {{pageClass}}" ng-view></div> ng-view不多说,class通过双向绑定的方式,通过controller动态控制class中的{{pageclass}} 另外不要忘了

angular的双向数据绑定

方向1:模型数据(model) 绑定 到视图(view) 实现方法1:{{model变量名}} $scope.num=10 <p>{{num}}</p> 实现方法2: 常用指令(ngRepeat.ngIf.ngShow/Hide/Src....) $scope.list=[{name:'sam',age:22},{name:'tom',age:37},{name:'kim',age:28}] <tr ng-repeat='std in list'> <td>

angular和vue双向数据绑定

angular和vue双向数据绑定的原理(重点是vue的双向绑定) 我在整理javascript高级程序设计的笔记的时候看到面向对象设计那章,讲到对象属性分为数据属性和访问器属性,我们平时用的js对象90%以上都只是用到数据属性;我们向来讲解下数据属性和访问器属性到底是什么? 数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值. 访问器属性:访问器属性不包含数据值;他们包含一对getter和setter函数在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值,在写

(八)简单了解下angularJS框架中NB的双向数据绑定机制,大大减少需要重复的开发代码量

之前写的第一篇angularJS入门文章 ,介绍ng-model的时候提到:使用angularJS的双向数据绑定机制,不需要我们编写繁琐的代码来实现同样的功能.现在我们看一个比较震撼的例子,看看angularJS是如何减少我们在前端开发中的繁琐劳动的.越是感受到框架功能的强大,越是能够激发学习的兴趣和动力. 假如我们有一个学生信息列表,包含学生的姓名.地址和年龄信息.假如这个数据源信息保存在data.js文件中. var g_phones = [ <span style="white-sp

vue和angular双向数据绑定原理

都是视图和数据的双向传递: angular双向数据绑定原理: 就是通过脏值检测的方式判断数据是否有变更: 当数据中的值改变的化,就会到$degiest(是vue内部的方法)中循环查找,当值不改变了,就会把数据显示到视图中: vue双向数据绑定原理: 数据劫持,使用ES5的Object.definpropoty() 方法监控的数据,数据的读取使用的是setter和getter,用于视图和数据的同步绑定:

Angular 1.63 双向数据绑定 通过 $http 发送数据

html 部分 <body ng-app="app"> <form action="" method="">账号 <div ng-controller="login"> <label for=""><input type="text" ng-model="data.name" name="name&quo

Angular数据双向绑定

Angular数据双向绑定 AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多款产品当中.AngularJS有着诸多特性,最为核心的是:MVVM.模块化.自动化双向数据绑定.语义化标签.依赖注入等等. 一.什么是数据双向绑定 Angular实现了双向绑定机制.所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面. 一个最简单的示例就是这样: <div ng-control