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

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

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

myobject.myproperty="new value";

当前回答

我自己也有一段时间对此感到奇怪。没有setters,AngularJS如何注意到$scope对象的更改?它会投票吗?

它实际上是这样做的:您修改模型的任何“正常”位置都已经从AngularJS的内部调用,因此它会在代码运行后自动为您调用$apply。假设你的控制器有一个方法,它连接到某个元素上的ng点击。因为AngularJS为您将该方法的调用连接在一起,所以它有机会在适当的位置执行$apply。同样,对于出现在视图中的表达式,这些表达式由AngularJS执行,因此它执行$apply。

当文档谈到必须为AngularJS之外的代码手动调用$apply时,它指的是运行时不源自调用堆栈中AngularJS本身的代码。

其他回答

Angular.js为我们在视图中创建的每个模型创建一个观察者。每当一个模型被更改时,一个“ng dirty”类就会被附加到该模型上,因此观察者将观察所有具有“ng dirt”类的模型,并在控制器中更新它们的值,反之亦然。

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

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

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

下面是使用输入字段与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。

希望这有帮助。

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

Misko已经很好地描述了数据绑定的工作方式,但我想补充一下我对数据绑定性能问题的看法。

正如Misko所说,大约2000个绑定是您开始发现问题的地方,但无论如何,页面上的信息不应该超过2000条。这可能是正确的,但并不是每个数据绑定都对用户可见。一旦开始使用双向绑定构建任何类型的小部件或数据网格,就可以很容易地达到2000个绑定,而不会出现糟糕的UX。

例如,考虑一个组合框,您可以在其中键入文本以筛选可用选项。这种控件可能有150个项目,并且仍然非常有用。如果它有一些额外的特性(例如,当前所选选项上的特定类),则开始为每个选项提供3-5个绑定。在一个页面上放置三个小部件(例如,一个用于选择国家,另一个用于在所述国家中选择城市,第三个用于选择酒店),您已经绑定了1000到2000个绑定。

或者考虑企业web应用程序中的数据网格。每页50行不是不合理的,每行可以有10-20列。如果您使用ng重复进行构建,和/或在某些使用某些绑定的单元格中有信息,那么仅使用此网格就可能接近2000个绑定。

在使用AngularJS时,我发现这是一个巨大的问题,到目前为止,我能找到的唯一解决方案是在不使用双向绑定的情况下构造小部件,而不是使用ngOnce、注销观察者和类似的技巧,或者构造用jQuery和DOM操纵构建DOM的指令。我觉得这一点一开始就违背了使用Angular的目的。

我很想听听关于处理这个问题的其他方法的建议,但也许我应该自己写一个问题。我想在评论中发表这一点,但事实证明这太长了。。。

TL;博士数据绑定可能会导致复杂页面的性能问题。

显然,并没有定期检查作用域中附加的对象是否有任何变化。并不是所有附加到作用域的对象都被监视。范围原型通常维护一个$$观察者。当调用$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();
       }
};