AngularJS框架中的数据绑定是如何工作的?

我还没有在他们的网站上找到技术细节。当数据从视图传播到模型时,它是如何工作的,这或多或少是清楚的。但是AngularJS如何在没有setter和getter的情况下跟踪模型财产的更改?

我发现有JavaScript观察者可能会做这项工作。但Internet Explorer 6和Internet Explorer 7不支持它们。那么AngularJS如何知道我更改了例如以下内容,并将此更改反映在视图上?

myobject.myproperty="new value";

当前回答

显然,并没有定期检查作用域中附加的对象是否有任何变化。并不是所有附加到作用域的对象都被监视。范围原型通常维护一个$$观察者。当调用$digest时,作用域仅遍历此$$watcher。

Angular为每个$$观察者添加一个观察者

{{表达式}} — 在您的模板中(以及其他任何有表达式的地方),或者在我们定义ng模型时。$范围$watch('表达式/函数') — 在您的JavaScript中,我们只需附加一个范围对象来观察angular。

$watch函数接受三个参数:

第一个是一个观察函数,它只返回对象,或者我们可以添加一个表达式。第二个是侦听器函数,当对象发生更改时将调用该函数。所有诸如DOM更改的事情都将在该函数中实现。第三个是可选参数,它接受布尔值。如果它为真,angular deep会监视对象;如果它为假,angular只会对对象进行引用监视。$watch的大致实现如下

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

在Angular中有一个有趣的东西叫做Digest Cycle。$digest循环是调用$scope的结果$digest()。假设您通过ng-click指令更改处理程序函数中的$scope模型。在这种情况下,AngularJS通过调用$digest()自动触发$digest循环。除了ng-click之外,还有几个内置的指令/服务可以让您更改模型(例如ng-model、$timeout等)并自动触发$digest循环。$digest的大致实现如下所示。

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

如果我们使用JavaScript的setTimeout()函数来更新范围模型,Angular无法知道您可能会更改什么。在这种情况下,我们有责任手动调用$apply(),这将触发$digest循环。类似地,如果您有一个指令设置DOM事件侦听器并更改处理程序函数中的某些模型,则需要调用$apply()以确保更改生效。$apply的主要思想是,我们可以执行一些不知道Angular的代码,这些代码可能仍然会改变作用域上的内容。如果我们将代码包装在$apply中,它将负责调用$digest()。$apply()的粗略实现。

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};

其他回答

这是我的基本理解。这很可能是错误的!

通过传递函数(返回要watch)转换为$watch方法。必须在代码块内对关注的项目进行更改由$apply方法包装。在$apply结束时,调用$digest方法通过每一块手表和检查,看看它们是否发生了变化上次运行$digest时。如果发现任何更改,则再次调用摘要,直到所有更改稳定下来。

在正常开发中,HTML告诉AngularJS编译器为您创建手表,并且控制器方法已经在$apply中运行。所以对于应用程序开发人员来说,它是完全透明的。

AngularJS借助三个强大的函数来处理数据绑定机制:$watch()、$digital()和$apply()。大多数时候AngularJS都会调用$scope$watch()和$scope$digest(),但是在某些情况下,您可能需要手动调用这些函数来更新新值。

$watch():-

此函数用于观察$scope上变量的变化。它接受三个参数:表达式、监听器和相等对象,其中监听器和相等对象是可选参数。

$digest()-

此函数迭代$scope对象中的所有手表,及其子$scope对象(如果有)。当$digest()迭代时在手表上,它检查表达式的值是否改变。如果值已更改,AngularJS将使用新值和旧值。调用$digest()函数每当AngularJS认为有必要时。例如,在按钮之后或者在AJAX调用之后。在某些情况下,AngularJS不会为您调用$digest()函数。在这种情况下,你必须你自己说吧。

$apply()-

Angular确实会自动神奇地更新那些在AngularJS上下文中。当您在Angular上下文(如浏览器DOM事件、setTimeout、XHR或第三个party库),则需要通过手动调用$apply()。当$apply()函数调用完成时AngularJS在内部调用$digital(),因此所有数据绑定都是更新。

下面是使用输入字段与AngularJS进行数据绑定的示例。我稍后会解释

HTML代码

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

AngularJS代码

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

正如您在上面的示例中看到的,AngularJS使用ng模型来监听和监视HTML元素,尤其是输入字段上发生的情况。当事情发生时,做点什么。在我们的例子中,ng模型使用八字胡符号{{}}绑定到我们的视图。输入字段中键入的任何内容都会立即显示在屏幕上。这就是数据绑定的美妙之处,以最简单的形式使用AngularJS。

希望这有帮助。

请参阅此处的工作示例代码笔

AngularJs支持双向数据绑定。意味着您可以访问数据视图->控制器和控制器->视图

例如。

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O/P

Peter

您可以在ng模型中绑定数据,如:-2)

<input ng-model="name" />

<div> {{ name }} </div>

在上面的示例中,无论用户将给出什么输入,它都将在<div>标记中可见。

如果要将输入从html绑定到控制器:-3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

如果您想在控制器中使用输入名称,

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng模型绑定我们的视图并在表达式{{}}中呈现它。ng模型是在视图中显示给用户并与用户交互的数据。因此,很容易在AngularJs中绑定数据。

AngularJS记住该值并将其与以前的值进行比较。这是基本的脏检查。如果值发生变化,则触发变化事件。

$apply()方法调用$digital(),它是当您从非AngularJS世界转换到AngularJS时调用的方法。摘要只是一个简单的旧检查。它适用于所有浏览器,完全可以预测。

对比脏检查(AngularJS)和更改侦听器(KnockoutJS和Backbone.js):虽然脏检查可能看起来很简单,甚至效率很低(我稍后会讨论),但事实证明它始终在语义上是正确的,而更改侦听器有很多奇怪的角落情况,需要依赖项跟踪之类的东西来使其更加语义正确。KnockoutJS依赖跟踪是解决AngularJS没有的问题的一个聪明功能。

更改侦听器的问题:

这种语法非常糟糕,因为浏览器本身不支持它。是的,有代理,但它们在所有情况下都不是语义正确的,当然在旧浏览器上也没有代理。底线是,脏检查允许您执行POJO,而KnockoutJS和Backbone.js强制您从它们的类继承,并通过访问器访问数据。更改合并。假设您有一个项目数组。假设您想将项添加到数组中,因为您正在循环添加,每次添加时都会触发更改事件,这就是呈现UI。这对性能非常不利。您需要的是在最后只更新一次UI。更改事件过于精细。更改侦听器会立即触发setter,这是一个问题,因为更改侦听器可以进一步更改数据,从而触发更多的更改事件。这很糟糕,因为在堆栈上,可能会同时发生多个更改事件。假设您有两个数组,无论出于何种原因,它们都需要保持同步。您只能添加其中一个或另一个,但每次添加时都会引发一个更改事件,这会导致对世界的看法不一致。这是一个非常类似于线程锁定的问题,JavaScript避免了这个问题,因为每个回调都以独占方式执行,直到完成。更改事件打破了这一点,因为设置器可能会产生深远的后果,而这些后果不是故意的,也不是显而易见的,这会再次导致线程问题。事实证明,您想要做的是延迟侦听器的执行,并保证一次只运行一个侦听器,因此任何代码都可以自由更改数据,并且它知道在执行此操作时没有其他代码运行。

性能如何?

因此,我们似乎很慢,因为脏检查效率很低。这就是我们需要研究实数的地方,而不仅仅是理论论证,但首先我们来定义一些约束条件。

人类是:

慢-任何速度超过50毫秒的东西都是人类无法察觉的,因此可以被视为“瞬间”。有限-你不能在一个页面上向人类显示超过2000条信息。除此之外的任何东西都是非常糟糕的UI,人类无论如何都无法处理它。

所以真正的问题是:在50毫秒内,你可以在浏览器上进行多少次比较?这是一个很难回答的问题,因为许多因素都在起作用,但这里有一个测试案例:http://jsperf.com/angularjs-digest/6这创造了10000名观众。在现代浏览器上,这需要不到6毫秒的时间。在Internet Explorer 8上,大约需要40毫秒。正如你所看到的,即使是在现在速度较慢的浏览器上,这也不是问题。有一个警告:比较需要简单,以符合时间限制。。。不幸的是,在AngularJS中添加缓慢的比较太容易了,所以当你不知道自己在做什么时,很容易构建缓慢的应用程序。但我们希望通过提供一个检测模块来得到答案,它将向您显示哪些是比较缓慢的。

事实证明,视频游戏和GPU使用脏检查方法,特别是因为它是一致的。只要它们超过了监视器刷新率(通常为50-60 Hz,或每16.6-20 ms),任何超过这个速度的性能都是浪费,所以你最好画更多的东西,而不是提高FPS。