实例剖析AngularJS框架中数据的双向绑定运用_AngularJS

数据绑定

通过把一个文本输入框绑定到person.name属性上,就能把我们的应用变得更有趣一点。这一步建立起了文本输入框跟页面的双向绑定。

在这个语境里“双向”意味着如果view改变了属性值,model就会“看到”这个改变,而如果model改变了属性值,view也同样会“看到”这个改变。Angular.js 为你自动搭建好了这个机制。如果你好奇这具体是怎么实现的,请看我们之后推出的一篇文章,其中深入讨论了digest_loop 的运作。

要建立这个绑定,我们在文本输入框上使用ng-model 指令属性,像这样:

<div ng-controller="MyController">
 <input type="text" ng-model="person.name" placeholder="Enter your name" />
 <h5>Hello {{ person.name }}</h5>
</div>

现在我们建立好了一个数据绑定(没错,就这么容易),来看看view怎么改变model吧:

试试看:

当你在文本框里输入时,下面的名字也自动随之改变,这就展现了我们数据绑定的一个方向:从view到model。

我们也可以在我们的(客户端)后台改变model,看这个改变自动在前端体现出来。要展示这一过程,让我们在  MyController 的model里写一个计时器函数, 更新 $scope 上的一个数据。下面的代码里,我们就来创建这个计时器函数,它会在每秒计时(像钟表那样),并更新 $scope 上的clock变量数据:

app.controller('MyController', function($scope) {
 $scope.person = { name: "Ari Lerner" };
 var updateClock = function() {
  $scope.clock = new Date();
 };
 var timer = setInterval(function() {
  $scope.$apply(updateClock);
 }, 1000);
 updateClock();
});

可以看到,当我们改变model中clock变量的数据,view会自动更新来反映此变化。用大括号我们就可以很简单地让clock变量的值显示在view里:

<div ng-controller="MyController">
 <h5>{{ clock }}</h5>
</div>

互动

前面我们把数据绑定在了文本输入框上。请注意, 数据绑定并非只限于数据,我们还可以利用绑定调用 $scope 中的函数(这一点之前已经提到过)。

对按钮、链接或任何其他的DOM元素,我们都可以用另一个指令属性来实现绑定:ng-click 。这个 ng-click 指令将DOM元素的鼠标点击事件(即 mousedown 浏览器事件)绑定到一个方法上,当浏览器在该DOM元素上鼠标触发点击事件时,此被绑定的方法就被调用。跟上一个例子相似,这个绑定的代码如下:

<div ng-controller="DemoController">
 <h4>The simplest adding machine ever</h4>
 <button ng-click="add(1)" class="button">Add</button>
 <button ng-click="subtract(1)" class="button">Subtract</button>
 <h4>Current count: {{ counter }}</h4>
</div>

不论是按钮还是链接都会被绑定到包含它们的DOM元素的controller所有的 $scope 对象上,当它们被鼠标点击,Angular就会调用相应的方法。注意当我们告诉Angular要调用什么方法时,我们将方法名写进带引号的字符串里。

app.controller('DemoController', function($scope) {
 $scope.counter = 0;
 $scope.add = function(amount) { $scope.counter += amount; };
 $scope.subtract = function(amount) { $scope.counter -= amount; };
});

 请看:

$scope.$watch

$scope.$watch( watchExp, listener, objectEquality );

为了监视一个变量的变化,你可以使用$scope.$watch函数。这个函数有三个参数,它指明了”要观察什么”(watchExp),”在变化时要发生什么”(listener),以及你要监视的是一个变量还是一个对象。当我们在检查一个参数时,我们可以忽略第三个参数。例如下面的例子:

$scope.name = 'Ryan';

$scope.$watch( function( ) {
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log('$scope.name was updated!');
} );

AngularJS将会在$scope中注册你的监视函数。你可以在控制台中输出$scope来查看$scope中的注册项目。

你可以在控制台中看到$scope.name已经发生了变化 – 这是因为$scope.name之前的值似乎undefined而现在我们将它赋值为Ryan!

对于$wach的第一个参数,你也可以使用一个字符串。这和提供一个函数完全一样。在AngularJS的源代码中可以看到,如果你使用了一个字符串,将会运行下面的代码:

if (typeof watchExp == 'string' && get.constant) {
 var originalFn = watcher.fn;
 watcher.fn = function(newVal, oldVal, scope) {
  originalFn.call(this, newVal, oldVal, scope);
  arrayRemove(array, watcher);
 };
}

这将会把我们的watchExp设置为一个函数,它也自动返回作用域中我们已经制定了名字的变量。

$$watchers
$scope中的$$watchers变量保存着我们定义的所有的监视器。如果你在控制台中查看$$watchers,你会发现它是一个对象数组。

$$watchers = [
  {
    eq: false, // 表明我们是否需要检查对象级别的相等
    fn: function( newValue, oldValue ) {}, // 这是我们提供的监听器函数
    last: 'Ryan', // 变量的最新值
    exp: function(){}, // 我们提供的watchExp函数
    get: function(){} // Angular's编译后的watchExp函数
  }
];

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

$scope.$apply
当一个控制器/指令/等等东西在AngularJS中运行时,AngularJS内部会运行一个叫做$scope.$apply的函数。这个$apply函数会接收一个函数作为参数并运行它,在这之后才会在rootScope上运行$digest函数。

AngularJS的$apply函数代码如下所示:

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

上面代码中的expr参数就是你在调用$scope.$apply()时传递的参数 – 但是大多数时候你可能都不会去使用$apply这个函数,要用的时候记得给它传递一个参数。

下面我们来看看ng-keydown是怎么来使用$scope.$apply的。为了注册这个指令,AngularJS会使用下面的代码。

var ngEventDirectives = {};
forEach(
 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
 function(name) {
  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});
       });
      });
     };
    }
   };
  }];
 }
);

上面的代码做的事情是循环了不同的类型的事件,这些事件在之后可能会被触发并创建一个叫做ng-[某个事件]的新指令。在指令的compile函数中,它在元素上注册了一个事件处理器,它和指令的名字一一对应。当事件被出发时,AngularJS就会运行scope.$apply函数,并让它运行一个函数。

只是单向数据绑定吗?
上面所说的ng-keydown只能够改变和元素值相关联的$scope中的值 – 这只是单项数据绑定。这也是这个指令叫做ng-keydown的原因,只有在keydown事件被触发时,能够给与我们一个新值。

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

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

$scope.$watch(function ngModelWatch() {
  var value = ngModelGet($scope);

  //如果作用域模型值和ngModel值没有同步
  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函数中运行时,我们就会知道这个值是什么!

为什么我们的监听器没有被触发?
如果我们在$scope.$watch的监听器函数中停止这个监听,即使我们更新了$scope.name,该监听器也不会被触发。

正如前面所提到的,AngularJS将会在每一个指令的控制器函数中运行$scope.$apply。如果我们查看$scope.$apply函数的代码,我们会发现它只会在控制器函数已经开始被调用之后才会运行$digest函数 – 这意味着如果我们马上停止监听,$scope.$watch函数甚至都不会被调用!但是它究竟是怎样运行的呢?

$digest函数将会在$rootScope中被$scope.$apply所调用。它将会在$rootScope中运行digest循环,然后向下遍历每一个作用域并在每个作用域上运行循环。在简单的情形中,digest循环将会触发所有位于$$watchers变量中的所有watchExp函数,将它们和最新的值进行对比,如果值不相同,就会触发监听器。

当digest循环运行时,它将会遍历所有的监听器然后再次循环,只要这次循环发现了”脏值”,循环就会继续下去。如果watchExp的值和最新的值不相同,那么这次循环就会被认为发现了脏值。理想情况下它会运行一次,如果它运行超10次,你会看到一个错误。

因此当$scope.$apply运行的时候,$digest也会运行,它将会循环遍历$$watchers,只要发现watchExp和最新的值不相等,变化触发事件监听器。在AngularJS中,只要一个模型的值可能发生变化,$scope.$apply就会运行。这就是为什么当你在AngularJS之外更新$scope时,例如在一个setTimeout函数中,你需要手动去运行$scope.$apply():这能够让AngularJS意识到它的作用域发生了变化。

创建自己的脏值检查
到此为止,我们已经可以来创建一个小巧的,简化版本的脏值检查了。当然,相比较之下,AngularJS中实现的脏值检查要更加先进一些,它提供疯了异步队列以及其他一些高级功能。

设置Scope
Scope仅仅只是一个函数,它其中包含任何我们想要存储的对象。我们可以扩展这个函数的原型对象来复制$digest和$watch。我们不需要$apply方法,因为我们不需要在作用域的上下文中执行任何函数 – 我们只需要简单的使用$digest。我们的Scope的代码如下所示:

var Scope = function( ) {
  this.$$watchers = [];
};

Scope.prototype.$watch = function( ) {

};

Scope.prototype.$digest = function( ) {

};

我们的$watch函数需要接受两个参数,watchExp和listener。当$watch被调用时,我们需要将它们push进入到Scope的$$watcher数组中。

var Scope = function( ) {
  this.$$watchers = [];
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {

};

你可能已经注意到了,如果没有提供listener,我们会将listener设置为一个空函数 – 这样一来我们可以$watch所有的变量。

接下来我们将会创建$digest。我们需要来检查旧值是否等于新的值,如果二者不相等,监听器就会被触发。我们会一直循环这个过程,直到二者相等。这就是”脏值”的来源 – 脏值意味着新的值和旧的值不相等!

var Scope = function( ) {
  this.$$watchers = [];
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};

接下来,我们将创建一个作用域的实例。我们将这个实例赋值给$scope。我们接着会注册一个监听函数,在更新$scope之后运行$digest!

var Scope = function( ) {
  this.$$watchers = [];
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};

var $scope = new Scope();

$scope.name = 'Ryan';

$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log(newValue, oldValue);
} );

$scope.$digest();

成功了!我们现在已经实现了脏值检查(虽然这是最简单的形式)!上述代码将会在控制台中输出下面的内容:

Ryan undefined

这正是我们想要的结果 – $scope.name之前的值是undefined,而现在的值是Ryan。

现在我们把$digest函数绑定到一个input元素的keyup事件上。这就意味着我们不需要自己去调用$digest。这也意味着我们现在可以实现双向数据绑定!

var Scope = function( ) {
  this.$$watchers = [];
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};

var $scope = new Scope();

$scope.name = 'Ryan';

var element = document.querySelectorAll('input');

element[0].onkeyup = function() {
  $scope.name = element[0].value;

  $scope.$digest();
};

$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log('Input value updated - it is now ' + newValue);

  element[0].value = $scope.name;
} );

var updateScopeValue = function updateScopeValue( ) {
  $scope.name = 'Bob';
  $scope.$digest();
};

使用上面的代码,无论何时我们改变了input的值,$scope中的name属性都会相应的发生变化。这就是隐藏在AngularJS神秘外衣之下数据双向绑定的秘密!

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript
, js
, angular
, angularjs
双向绑定
angularjs 框架 实例、angularjs 双向绑定、angularjs2 双向绑定、angularjs的双向绑定、angularjs小项目实例,以便于您获取更多的相关知识。

时间: 2024-11-02 04:35:12

实例剖析AngularJS框架中数据的双向绑定运用_AngularJS的相关文章

深入学习AngularJS中数据的双向绑定机制_AngularJS

Angular JS (Angular.JS) 是一组用来开发Web页面的框架.模板以及数据绑定和丰富UI组件.它支持整个开发进程,提供web应用的架构,无需进行手工DOM操作. AngularJS很小,只有60K,兼容主流浏览器,与 jQuery 配合良好.双向数据绑定可能是AngularJS最酷最实用的特性,将MVC的原理展现地淋漓尽致. AngularJS的工作原理是:HTML模板将会被浏览器解析到DOM中, DOM结构成为AngularJS编译器的输入.AngularJS将会遍历DOM模

深入探究AngularJS框架中Scope对象的超级教程_AngularJS

一.遇到的问题问题发生在使用 AngularJS 嵌套 Controller 的时候.因为每个 Controller 都有它对应的 Scope(相当于作用域.控制范围),所以 Controller 的嵌套,也就意味着 Scope 的嵌套.这个时候如果两个 Scope 内都有同名的 Model 会发生什么呢?从子 Scope 怎样更新父 Scope 里的 Model 呢? 这个问题很典型,比方说当前页面是一个产品列表,那么就需要定义一个 ProductListController function

Vue.js第一天学习笔记(数据的双向绑定、常用指令)_javascript技巧

数据的双向绑定(ES6写法)  效果: 没有改变 input 框里面的值时: 将input 框里面的值清空时: 重新给  input 框输入  豆豆 后页面中  span  里绑定{{testData.name}}的值随着 input 框值的变化而变化. 在Vue.js中可以使用v-model指令在表单元素上创建双向数据绑定.并且v-model指令只能用于:<input>.<select>.<textarea>这三种标签. <template> <di

combo box-WPF中的combobox双向绑定问题

问题描述 WPF中的combobox双向绑定问题 我想当它发生改变的时候,我后台数值也会发生改变.于是我这样写: 运行都是OK的.可是我想让他真的变成绑定, 就是用itemssour = binding那个.怎么能修改下呢 解决方案 WPF的Combobox的ItemSource第二次绑定(更新)问题WPF 的COMBOBOX绑定(匿名类)WPF 数据绑定 combobox 解决方案二: http://www.cnblogs.com/mantian/archive/2013/01/22/2871

在AngularJS框架中处理数据建模的方式解析_AngularJS

我们知道,AngularJS并没有自带立等可用的数据建模方案.而是以相当抽象的方式,让我们在controller中使用JSON数据作为模型.但是随着时间的推移和项目的成长,我意识到这种建模的方式不再能满足我们项目的需求.在这篇文章中我会介绍在我的AngularJS应用中处理数据建模的方式. 为Controller定义模型 让我们从一个简单的例子开始.我想要显示一个书本(book)的页面.下面是控制器(Controller): BookController app.controller('Book

详解JavaScript的AngularJS框架中的作用域与数据绑定_AngularJS

AngularJS 简介AngularJS 是由 Google 发起的一款开源的前端 MVC 脚本框架,既适合做普通 WEB 应用也可以做 SPA(单页面应用,所有的用户操作都在一个页面中完成).与同为 MVC 框架的 Dojo 的定位不同,AngularJS 在功能上更加轻量,而相比于 jQuery,AngularJS 又帮您省去了许多机械的绑定工作.在一些对开发速度要求高,功能模块不需要太丰富的非企业级 WEB 应用上,AngularJS 是一个非常好的选择.AngularJS 最为复杂同时

深入解析AngularJS框架中$scope的作用与生命周期_AngularJS

    $scope 的使用贯穿整个 Angular App 应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope 就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope 发生改变时也会立刻重新渲染视图.     有了 $scope 这样一个桥梁,应用的业务代码可以都在 controller 中,而数据都存放在controller 的 $scope 中. $scope是一个把view(一个DOM元素)连结到controll

详解JavaScript的AngularJS框架中的表达式与指令_AngularJS

"指令属性"就是绑定在DOM元素上的函数,它可以调用方法.定义行为.绑定controller及$scope对象.操作DOM,等等等等. 当浏览器启动.开始解析HTML(像平时一样)时,DOM元素上的指令属性就会跟其他属性一样被解析. 当一个Angular.js应用启动,Angular编译器就会遍历DOM树(从有ng-app指令属性的那个DOM元素开始,如我们在本系列第一篇里所提过的),解析HTML,寻找这些指令属性函数. 当在一个DOM元素上找到一个或多个这样的指令属性函数,它们就会被

AngularJS指令中的绑定策略实例分析_AngularJS

本文实例讲述了AngularJS指令中的绑定策略.分享给大家供大家参考,具体如下: 在前面的文章中,我们知道了指令如何生成独立的scope,这一节中我们来仔细研究一下scope中的绑定策略. 总体来说scope的绑定策略分为3种: (1)@ : 绑定字符串 (2)=: 与父控制器进行双向绑定 (3)&:用于调用父scope中的函数 1.基础方式 <test word="{{wordCtrl}}"></test> app.controller('myCon