How does data binding work in AngularJS?
数据绑定在
我在他们的网站上没有找到技术细节。当数据从一个视图传播到另一个模型时,它的工作方式或多或少是清晰的。但是,在没有setter和getter的情况下,AngularJS如何跟踪模型属性的更改?
我发现有javascript观察程序可以完成这项工作。但Internet Explorer 6和Internet Explorer 7不支持这些功能。那么AngularJS如何知道我做了更改,例如下面的更改,并将此更改反映在视图上?
1 | myobject.myproperty="new value"; |
AngularJS记住该值并将其与以前的值进行比较。这是基本的脏检查。如果值发生了更改,那么它将触发更改事件。好的。
当从非AngularJS世界过渡到AngularJS世界时,您所调用的
对比脏检查(angularjs)和变更监听器(knockoutjs和主干网.js):虽然脏检查看起来很简单,甚至效率低下(稍后我将讨论这个问题),但结果表明它始终在语义上是正确的,而变更监听器有许多奇怪的角落情况,需要依赖性跟踪之类的东西来使其更为简单。完全正确。对于AngularJS没有的问题,knockoutJS依赖性跟踪是一个聪明的特性。好的。更改侦听器的问题:
- 语法很糟糕,因为浏览器不支持它。是的,有代理,但在所有情况下,它们在语义上都不正确,当然在旧浏览器上没有代理。底线是脏检查允许您执行pojo,而knockoutjs和主干.js则强制您从它们的类继承,并通过访问器访问数据。
- 改变融合。假设您有一个项目数组。假设您希望向数组中添加项,当您正在循环添加时,每次添加时都会触发正在呈现UI的更改事件。这对性能非常不利。最后,您只需要更新一次UI。更改事件的粒度太细。
- 变更监听器会立即触发setter,这是一个问题,因为变更监听器可以进一步变更数据,从而触发更多的变更事件。这很糟糕,因为在您的堆栈中,您可能同时发生多个更改事件。假设您有两个数组,无论出于什么原因都需要保持同步。您只能向其中一个添加内容,但每次添加内容时,都会触发一个变更事件,现在该事件对世界的看法不一致。这是一个与线程锁定非常相似的问题,因为每次回调都是以独占方式执行并完成的,所以JavaScript避免了线程锁定问题。变更事件打破了这一点,因为setter可能产生深远的后果,这些后果不是有意的,也不是显而易见的,这会再次造成线程问题。事实证明,您要做的是延迟侦听器的执行,并保证一次只运行一个侦听器,因此任何代码都可以自由地更改数据,并且它知道在执行此操作时没有其他代码运行。
那表演呢?
因此,我们似乎很慢,因为脏的检查效率很低。这就是我们需要研究实数而不仅仅是理论上的论点的地方,但首先让我们定义一些约束条件。好的。
人类是:好的。
慢-任何超过50毫秒的速度都是人类无法察觉的,因此可以被视为"瞬间"。好的。
有限-你不能在一个页面上向一个人展示超过2000条信息。除此之外的任何东西都是非常糟糕的用户界面,人类无论如何都无法处理它。好的。
因此,真正的问题是:在50毫秒内,您可以在浏览器上进行多少次比较?这是一个很难回答的问题,因为有很多因素在起作用,但这里有一个测试案例:http://jspef.com/angularjs-digest/6,它创建了10000个观察者。在现代浏览器上,这只需要不到6毫秒。在Internet Explorer 8上,这只需要大约40毫秒。正如您所看到的,即使在现在速度较慢的浏览器上,这也不是问题。有一个警告:比较需要简单,以适应时间限制…不幸的是,在AngularJS中添加一个缓慢的比较太容易了,所以当您不知道自己在做什么时,很容易构建缓慢的应用程序。但是我们希望通过提供一个工具模块得到一个答案,这个模块将向您显示哪些比较缓慢。好的。
事实证明,视频游戏和GPU使用脏检查方法,特别是因为它是一致的。只要他们超过了监视器刷新率(通常为50-60赫兹,或每16.6-20毫秒),任何超过这一点的性能都是浪费,所以你最好画更多的东西,而不是提高fps。好的。好啊。
Misko已经对数据绑定的工作方式进行了很好的描述,但是我想添加关于数据绑定的性能问题的视图。
正如misko所说,大约2000个绑定是您开始发现问题的地方,但是一个页面上的信息不应该超过2000条。这可能是正确的,但并非每个数据绑定对用户都可见。一旦开始使用双向绑定构建任何类型的小部件或数据网格,您就可以轻松地访问2000个绑定,而不会有坏的UX。
例如,考虑一个组合框,您可以在其中键入文本以筛选可用选项。这种类型的控件可以有大约150个项,并且仍然是高度可用的。如果它有一些额外的特性(例如当前所选选项上的特定类),那么您将开始为每个选项获取3-5个绑定。将这些小部件中的三个放到一个页面上(例如,一个用于选择国家,另一个用于选择所述国家的城市,第三个用于选择酒店),您的绑定已经介于1000到2000之间。
或者考虑公司Web应用程序中的数据网格。每页50行不是不合理的,每行可以有10-20列。如果使用ng repeats构建它,并且/或者在某些使用某些绑定的单元格中有信息,那么仅使用此网格就可以接近2000个绑定。
在使用AngularJS时,我发现这是一个很大的问题,到目前为止,我能找到的唯一解决方案是在不使用双向绑定的情况下构造小部件,而不是使用Ngonce、取消注册观察者和类似技巧,或者构造使用jquery和dom操作构建dom的指令。我觉得这一点一开始就破坏了使用角的目的。
我很想听听其他方法的建议,但也许我应该写自己的问题。我想把这个写在评论里,但结果太长了…
DR<BR/>数据绑定可能会导致复杂页面出现性能问题。
通过脏检查
Angular在
每个观察者都是一个包含其他内容的
如何定义观察者
在AngularJS中定义观察者有许多不同的方法。好的。
您可以在
$scope 上显式地使用$watch 和attribute 。好的。1$scope.$watch('person.username', validateUnique);您可以在模板中放置一个
{{}} 插值(将在当前$scope 上为您创建一个观察程序)。好的。1
2
3<p>
username: {{person.username}}
</p>Ok.您可以请求诸如
ng-model 之类的指令为您定义观察者。好的。1<input ng-model="person.username" />
当我们通过正常通道(ng模型、ng重复等)与angularjs交互时,指令将触发一个消化循环。好的。
摘要循环是
遍历每个范围,并根据最后一个值对每个监视表达式进行计算和检查。好的。如果触发了观察程序,则
如果一个观察者被触发,应用程序就会知道发生了什么变化,并且
观察程序函数可以更改
这是因为AngularJS具有双向绑定,所以可以将数据传回
我们不断地循环遍历
如果达到摘要限制,AngularJS将在控制台中引发错误:好的。
1 | 10 $digest() iterations reached. Aborting! |
摘要在机器上很难,但在开发人员上很容易
如您所见,每当AngularJS应用程序发生变化时,AngularJS将检查
从机器的角度来看,虽然这是非常低效的,如果我们创建了太多的观察者,将会减慢我们的应用程序的速度。Misko引用了大约4000个观察者的数据,在你的应用程序在老版本的浏览器上会感觉缓慢之前。好的。
例如,如果您在一个大的
每次用户与应用程序交互时,应用程序中的每个观察者都将至少评估一次。优化AngularJS应用程序的很大一部分是减少
如果有很少更改的数据,则只能使用::语法绑定一次,如下所示:好的。
1 2 3 | <p> {{::person.username}} </p>Ok. |
或好的。
1 2 | <p ng-bind="::person.username"> </p>Ok. |
只有在呈现包含模板并将数据加载到
当您有一个包含许多项目的
1 | {{::person.username}} |
好啊。
这是我的基本理解。很可能是错的!
在正常的开发中,HTML中的数据绑定语法告诉AngularJS编译器为您创建监视,控制器方法已经在
我自己想了一会儿。没有设置者,
它实际上是这样做的:修改模型的任何"正常"地方都已经从
当文档谈到必须手动调用
用图片解释:
数据绑定需要映射作用域中的引用不是模板中的引用。当数据绑定两个对象时,需要第三个对象来监听第一个对象并修改另一个对象。
在这里,当您修改
角形保持每个结合的
在上一张图片中,它将注意到data-ref1和data-ref2已更改。
结论有点像鸡蛋和鸡肉。你永远不知道是谁开始的,但希望它能像预期的那样在大多数时候发挥作用。
另一点是,您可以轻松理解简单绑定对内存和CPU的影响。希望台式机足够胖来处理这个问题。手机不那么结实。
显然,没有对
Angular为每个$watchers添加了一个观察者
{{expression}}?—?In your templates (and anywhere else where there’s an expression) or when we define ng-model. $scope.$watch(‘expression/function’)?—?In your JavaScript we can just attach a scope object for angular to watch.
$watch函数接受三个参数:
First one is a watcher function which just returns the object or we can just add an expression.
Second one is a listener function which will be called when there is a change in the object. All the things like DOM changes will be implemented in this function.
The third being an optional parameter which takes in a boolean . If its true , angular deep watches the object & if its false Angular just does a reference watching on the object.
Rough Implementation of $watch looks like this
1 2 3 4 5 6 7 8 | 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 }; |
在角度上有一个有趣的东西叫做消化循环。$digest循环开始于对$scope.$digest()的调用。假设您通过ng click指令更改了handler函数中的$scope模型。在这种情况下,AngularJS通过调用$Digest()自动触发$Digest循环。除了ng click,还有其他几个内置指令/服务允许您更改模型(例如ng model、$timeout等),并自动触发$Digest循环。$digest的粗略实现如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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的主要思想是我们可以执行一些不知道角度的代码,这些代码可能仍然会改变范围。如果用$apply包装该代码,它将负责调用$digest()。$Apply()的粗略实现。
1 2 3 4 5 6 7 | Scope.prototype.$apply = function(expr) { try { return this.$eval(expr); //Evaluating code in the context of Scope } finally { this.$digest(); } }; |
AngularJS在三个强大函数的帮助下处理数据绑定机制:$watch()、$digest()和$apply()。大多数时候,AngularJS会调用$scope.$watch()和$scope.$digest(),但是在某些情况下,您可能需要手动调用这些函数以使用新值进行更新。
$WAT():
This function is used to observe changes in a variable on the $scope.
It accepts three parameters: expression, listener and equality object,
where listener and equality object are optional parameters.
$Digeste()
This function iterates through all the watches in the $scope object,
and its child $scope objects
(if it has any). When $digest() iterates
over the watches, it checks if the value of the expression has
changed. If the value has changed, AngularJS calls the listener with
new value and old value. The $digest() function is called
whenever AngularJS thinks it is necessary. For example, after a button
click, or after an AJAX call. You may have some cases where AngularJS
does not call the $digest() function for you. In that case you have to
call it yourself.
$Apple()
Angular do auto-magically updates only those model changes which are
inside AngularJS context. When you do change in any model outside of
the Angular context (like browser DOM events, setTimeout, XHR or third
party libraries), then you need to inform Angular of the changes by
calling $apply() manually. When the $apply() function call finishes
AngularJS calls $digest() internally, so all data bindings are
updated.
碰巧我需要将一个人的数据模型与表单链接起来,我所做的是将数据与表单直接映射。
例如,如果模型有如下内容:
1 | $scope.model.people.name |
表单的控件输入:
1 | <input type="text" name="namePeople" model="model.people.name"> |
这样,如果修改对象控制器的值,这将自动反映在视图中。
我传递模型的一个例子是从服务器数据更新,当您请求基于写入的邮政编码和邮政编码时,加载与该视图相关联的殖民地和城市列表,并且默认情况下与用户设置第一个值。我做得很好,实际情况是,
单向数据绑定是一种从数据模型中获取值并将其插入HTML元素的方法。无法从视图更新模型。在经典模板系统中使用。这些系统只在一个方向上绑定数据。
Angular应用程序中的数据绑定是模型和视图组件之间数据的自动同步。
数据绑定允许您将模型视为应用程序中的单一真实源。视图始终是模型的投影。如果模型被更改,视图将反映更改,反之亦然。
AngularJS支持双向数据绑定。意味着您可以访问数据视图->控制器和控制器->视图
为Ex.
1)
1 2 3 4 5 | // If $scope have some value in Controller. $scope.name ="Peter"; // HTML {{ name }} |
O/P
1 | Peter |
您可以在
1 2 3 | <input ng-model="name" /> {{ name }} |
在上面的示例中,无论用户给出什么样的输入,它都将在
如果要将输入从HTML绑定到控制器:3)
1 2 3 4 | <form name="myForm" ng-submit="registration()"> <label> Name </lbel> <input ng-model="name" /> </form> |
如果您想在控制器中使用输入
1 2 3 4 5 | $scope.name = {}; $scope.registration = function() { console.log("You will get the name here", $scope.name); }; |
下面是使用输入字段与AngularJS进行数据绑定的示例。我稍后解释
HTML代码
1 2 3 4 | <input type="text" ng-model="watchInput" Placeholder="type something"/> <p> {{watchInput}} </p> |
盎格鲁JS码
1 2 3 4 | myApp = angular.module ("myApp", []); myApp.controller("myCtrl", ["$scope", function($scope){ //Your Controller code goes here }]); |
正如您在上面的示例中看到的,AngularJS使用
希望这有帮助。
请参阅下面的一个工作示例科德森
angular.js为我们在视图中创建的每个模型创建一个观察程序。每当模型更改时,"ng dirty"类将应用于模型,因此观察者将观察具有"ng dirty"类的所有模型,并更新控制器中的值,反之亦然。
数据绑定:
什么是数据绑定?
每当用户更改视图中的数据时,作用域模型和viceversa中都会更新该更改。
怎么可能?
简短回答:在消化循环的帮助下。
描述:AngularJS在作用域模型上设置观察程序,如果模型发生更改,它将激发侦听器函数。
1 | $scope.$watch('modelVar' , function(newValue,oldValue){ |
//DOM用新值更新代码
(});
那么何时以及如何调用watcher函数呢?
观察程序函数作为摘要循环的一部分被调用。
摘要循环是作为AngularJS内置指令/服务的一部分自动触发的,如ng model、ng bind、$timeout、ng click和其他。让你触发消化循环。
消化循环功能:
1 2 | $scope.$digest() -> digest cycle against the current scope. $scope.$apply() -> digest cycle against the parent scope |
I.E
注:$apply()等于$root scope.$digest(),这意味着脏检查从根、顶部或父作用域开始,一直到Angular JS应用程序中的所有子作用域。
上述功能在浏览器IE中也适用于上述版本,只需确保您的应用程序是AngularJS应用程序,这意味着您使用的是脚本标记中引用的AngularJS框架脚本文件。
谢谢您。