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

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

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

myobject.myproperty="new value";

当前回答

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

希望这有帮助。

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

其他回答

数据绑定:

什么是数据绑定?

每当用户更改视图中的数据时,范围模型中就会出现该更改的更新,反之亦然。

怎么可能?

简短回答:借助消化循环。

说明:Angular js在作用域模型上设置观察者,如果模型发生变化,它会触发监听器函数。

$scope.$watch('modelVar' , function(newValue,oldValue){

//使用新值更新Dom代码

});

那么何时以及如何调用观察程序函数?

作为摘要循环的一部分调用观察程序函数。

摘要循环是作为angular js内置指令/服务的一部分自动触发的,如ng model、ng bind、$timeout、ng click等。。让你触发消化周期。

摘要循环功能:

$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope 

即$rootScope.$apply()

注:$apply()等于$rootScope$digest()这意味着脏检查从根或顶部或父作用域开始,一直到angular js应用程序中的所有子$scopes。

上述功能适用于上述版本的浏览器IE,只需确保您的应用程序是angular js应用程序,这意味着您使用的是脚本标记中引用的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;博士数据绑定可能会导致复杂页面的性能问题。

通过脏检查$scope对象

Angular在$scope对象中维护一个简单的观察者数组。如果您检查任何$scope,就会发现它包含一个名为$$watcher的数组。

每个观察者都是一个对象,其中包含其他内容

观察程序正在监视的表达式。这可能只是一个属性名称,或者更复杂的东西。表达式的最后一个已知值。这可以根据表达式的当前计算值进行检查。如果值不同,观察者将触发函数并将$scope标记为dirty。如果观察程序是脏的,将执行的函数。

如何定义观察者

在AngularJS中有许多不同的定义观察者的方法。

您可以在$scope上显式地$watch属性。$范围$watch('person.username',validateUnique);您可以在模板中放置{{}}插值(将在当前$scope上为您创建一个观察程序)。<p>用户名:{{person.username}}</p>您可以要求一个指令(如ng模型)为您定义观察者。<input ng model=“person.username”/>

$digest周期检查所有观察者的最后值

当我们通过正常通道(ng模型、ng重复等)与AngularJS交互时,指令将触发摘要循环。

摘要循环是对$scope及其所有子级的深度优先遍历。对于每个$scope对象,我们迭代其$$watcher数组并计算所有表达式。如果新的表达式值与上一个已知值不同,则调用观察者的函数。这个函数可能会重新编译DOM的一部分,重新计算$scope上的值,触发AJAX请求,任何您需要它做的事情。

遍历每个作用域,并根据最后一个值计算和检查每个监视表达式。

如果触发了观察者,则$scope是脏的

如果触发了观察者,则应用程序知道发生了变化,$scope被标记为dirty。

观察者函数可以更改$scope或父$scope上的其他属性。如果触发了一个$watcher函数,我们不能保证我们的其他$scope仍然是干净的,因此我们再次执行整个摘要循环。

这是因为AngularJS具有双向绑定,因此可以将数据传递回$scope树。我们可能会更改已经消化的较高$scope的值。也许我们更改了$rootScope上的值。

如果$digest是脏的,我们将再次执行整个$digest循环

我们不断循环$digest循环,直到消化循环干净(所有$watch表达式的值与上一个循环中的值相同),或者达到消化极限。默认情况下,此限制设置为10。

如果我们达到摘要限制,AngularJS将在控制台中引发错误:

10 $digest() iterations reached. Aborting!

摘要对机器来说很难,但对开发人员来说很容易

正如您所看到的,每当AngularJS应用程序发生变化时,AngularJS都会检查$scope层次结构中的每个观察者,以查看如何响应。对于开发人员来说,这是一个巨大的生产力优势,因为您现在几乎不需要编写任何布线代码,AngularJS只会注意到值是否发生了变化,并使应用程序的其余部分与变化保持一致。

从机器的角度来看,这是非常低效的,如果我们创建了太多的观察者,会减慢我们的应用程序的速度。Misko引用了大约4000名观察者的数据,在你的应用程序在较旧的浏览器上运行缓慢之前。

例如,如果在大型JSON数组上重复,则很容易达到此限制。您可以使用一次性绑定等功能来编译模板,而无需创建观察者。

如何避免创建过多观察者

每次用户与应用程序交互时,应用程序中的每个观察者都将至少进行一次评估。优化AngularJS应用程序的很大一部分是减少$scope树中的观察者数量。一种简单的方法是一次性绑定。

如果您有很少更改的数据,则只能使用::语法将其绑定一次,如下所示:

<p>{{::person.username}}</p>

or

<p ng-bind="::person.username"></p>

只有当呈现包含模板并将数据加载到$scope中时,才会触发绑定。

当你重复很多项目时,这一点尤为重要。

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>

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(),因此所有数据绑定都是更新。

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

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

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