数据绑定如何在AngularJS中工作?

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

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

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

myobject.myproperty="new value";
356560 次浏览

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

  1. 通过传递一个函数来监视项目(返回要执行的内容)注意)到$watch方法。
  2. 对监视项目的更改必须在代码块内进行由$apply方法包装。
  3. $apply的末尾调用$digest方法,如下所示检查每一个手表和支票看它们有没有变化上一次$digest跑了。
  4. 如果发现任何更改,则再次调用摘要,直到所有更改稳定。

在正常的开发中,超文本标记语言中的数据绑定语法告诉AngularJS编译器为你创建手表,控制器方法已经在$apply中运行。所以对应用程序开发人员来说,这一切都是透明的。

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

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

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

更改侦听器的问题:

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

表现如何?

因此,我们可能看起来很慢,因为脏检查效率低下。这是我们需要查看实数而不仅仅是理论论证的地方,但首先让我们定义一些约束。

人类是:

  • -任何超过50毫秒的速度都是人类无法察觉的,因此可以被认为是“瞬间”。

  • Limited-你不能在一个页面上向人类显示超过2000条的信息。除此之外的任何内容都是非常糟糕的UI,人类无论如何都无法处理。

所以真正的问题是:在50毫秒内,你能在浏览器上做多少次比较?这是一个很难回答的问题,因为有很多因素在起作用,但这里有一个测试用例:http://jsperf.com/angularjs-digest/6它创建了10,000个观察者。在现代浏览器上,这需要不到6毫秒。在Internet Explorer 8上,它需要大约40毫秒。正如你所看到的,即使在这些天很慢的浏览器上,这也不是一个问题。有一个警告:比较需要简单,以适应时间限制…不幸的是,在AngularJS中添加慢速比较太容易了,所以当你不知道自己在做什么时,很容易构建慢速应用程序。但我们希望通过提供一个仪器模块来找到答案,它会告诉你哪些是慢速比较。

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

我自己也想了一会儿。没有setter,AngularJS如何注意到$scope对象的变化?它会轮询它们吗?

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

当留档谈到必须手动调用$apply以获取代码在#1之外时,它所谈论的代码在运行时并不源于调用堆栈中的AngularJS本身。

Misko已经很好地描述了数据绑定的工作原理,但我想添加我对数据绑定性能问题的看法。

正如Misko所说,大约2000个绑定是你开始看到问题的地方,但无论如何你不应该在一个页面上有超过2000条的信息。这可能是真的,但不是每个数据绑定都是用户可见的。一旦你开始构建任何类型的小部件或具有双向绑定的数据网格,你可以容易达到2000个绑定,而不会有糟糕的用户体验。

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

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

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

我很乐意听到关于其他处理方式的建议,但也许我应该写我自己的问题。我想把这个放在评论中,但事实证明太长了…

太长别读
数据绑定可能会导致复杂页面上的性能问题。

碰巧我需要将一个人的数据模型与表单联系起来,我所做的是将数据与表单直接映射。

例如,如果模型具有以下内容:

$scope.model.people.name

表单的控制输入:

<input type="text" name="namePeople" model="model.people.name">

这样,如果您修改对象控制器的值,这将自动反映在视图中。

我传递的模型从服务器数据更新的一个例子是,当你要求邮政编码和邮政编码基于书面加载与该视图关联的殖民地和城市列表,并默认设置与用户的第一个值。这我工作得很好,发生了什么,是angularJS有时需要几秒钟来刷新模型,要做到这一点,你可以在显示数据时放一个微调器。

通过脏检查$scope对象

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

每个观察者都是一个object,其中包含

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

观察者是如何定义的

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

  • 您可以在$scope上显式地$watchattribute

      $scope.$watch('person.username', validateUnique);
  • 您可以在模板中放置\{\{}}插值(将在当前$scope上为您创建一个观察者)。

      <p>username: \{\{person.username}}</p>
  • 您可以要求ng-model之类的指令为您定义观察者。

      <input ng-model="person.username" />

$digest循环检查所有观察者的最后一个值

当我们与AngularJS通过正常通道(ng-模型,ng-重复等)进行交互时,指令将触发一个摘要周期。

摘要循环是一个#0及其所有子节点的深度优先遍历。对于每个$scopeobject,我们迭代它的$$watchersarray并评估所有表达式。如果新的表达式值与上次已知值不同,则调用观察者函数。这个函数可能会重新编译DOM的一部分,重新计算$scope上的一个值,触发AJAXrequest,任何你需要它做的事情。

遍历每个范围,并根据最后一个值评估和检查每个监视表达式。

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

如果触发了观察者,则应用程序知道某些内容发生了变化,并且$scope被标记为脏。

观察者函数可以更改$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名观察者的数字,然后你的应用程序在旧浏览器上会感觉很慢。

例如,如果您在大型JSONarrayng-repeat,则很容易达到此限制。您可以使用一次性绑定等功能来缓解这种情况,从而在不创建观察者的情况下编译模板。

如何避免创建过多的观察者

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

如果你有很少改变的数据,你只能使用:: 语法绑定它一次,如下所示:

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

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

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

当你有一个ng-repeat包含许多项目时,这一点尤其重要。

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

这是一个使用AngularJS的数据绑定示例,使用输入字段。我稍后会解释

超文本标记语言

<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-model来监听和观察超文本标记语言元素上发生的事情,尤其是在input字段上。当发生什么事情时,做点什么。在我们的例子中,ng-model使用胡子符号\{\{}}绑定到我们的视图。在输入字段中键入的任何内容都会立即显示在屏幕上。这就是数据绑定的美妙之处,以最简单的形式使用AngularJS。

希望这有帮助。

在此处查看一个工作示例coDepen

AngularJS在三个强大函数的帮助下处理数据绑定机制:$watch()$digest()$应用()。大多数时候AngularJS会调用$scope.$watch()和$ope.$digest(),但是在某些情况下,您可能需要手动调用这些函数以使用新值进行更新。

$watch():-

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

$digest()-

这个函数遍历$scope对象中的所有手表,和它的子$范围对象
(如果有的话)。当$digest()迭代时在手表上,它检查表达式的值是否具有改变了。如果值发生了变化,AngularJS用新值和旧值。$digest()函数被调用每当AngularJS认为有必要时。例如,在按钮之后单击,或在AJAX调用之后。您可能会遇到一些AngularJS不会为您调用$digest()函数。在这种情况下,您必须你自己叫吧

$应用()-

Angular只自动神奇地更新那些模型更改在AngularJS上下文中。当你在Angular上下文(如浏览器DOM事件、setTimeout、XHR或第三方库),然后您需要通过以下方式通知Angular更改手动调用$应用()。当$应用()函数调用完成时AngularJS内部调用$digest(),因此所有数据绑定都是更新中

用图片解释:

数据绑定需要映射

作用域中的引用并不完全是模板中的引用。当您对两个对象进行数据绑定时,您需要第三个对象监听第一个对象并修改另一个对象。

在此处输入图片描述

在这里,当你修改<input>时,你触摸了data-ref3。经典的数据绑定机制将改变data-ref4。那么其他\{\{data}}表达式将如何移动?

事件导致$digest()

在此处输入图片描述

Angular维护每个绑定的oldValuenewValue。在每个角事件之后,著名的$digest()循环将检查WatchList以查看是否有变化。这些newValue0是ng-clickng-change$http完成…只要任何oldValuenewValue不同,$digest()就会循环。

在上图中,它会注意到data-ref1和data-ref2发生了变化。

结论

这有点像鸡蛋和鸡肉。你永远不知道谁开始,但希望它能像预期的那样工作。

另一点是,您可以很容易地理解简单绑定对内存和CPU的影响。希望台式机足够胖来处理这个。手机没有那么强大。

显然,没有定期检查Scope附加到它的对象是否有任何变化。并非所有附加到范围的对象都被监视。范围原型通常维护$$观察者Scope仅在调用$digest时迭代此$$watchers

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

  1. \{\{表达式}}-在您的模板中(以及其他任何有表达式的地方)或当我们定义ng-model时。
  2. $watch('表达式/函数')-在您的JavaScript中,我们可以附加一个作用域对象以供角度观察。

$手表函数接受三个参数:

  1. 第一个是一个观察者函数,它只是返回对象,或者我们可以添加一个表达式。

  2. 第二个是侦听器函数,当对象发生变化时将调用它。所有像DOM更改这样的事情都将在这个函数中实现。

  3. 第三个是一个可选参数,它接受布尔值。如果它为真,则角深观察对象&如果它为假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循环是在调用$ope.$digest()时开始的。假设您通过ng-Click指令更改处理程序函数中的$ope模型。在这种情况下,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 checkingif (newValue !== oldValue) { //Dirty checking of References// For Deep checking the object , code of Value// based checking of Object should be implemented herewatcher.last = newValue;watcher.listenerFn(newValue,(oldValue === initWatchVal ? newValue : oldValue),self);dirty = true;}});return dirty;};

如果我们使用JavaScript的设置超时函数来更新作用域模型,Angular无法知道你可能会改变什么。在这种情况下,我们有责任手动调用美元应用(),这会触发一个美元摘要循环。类似地,如果你有一个指令设置一个DOM事件监听器并在处理程序函数内更改一些模型,你需要调用美元应用()来确保更改生效。美元应用的大想法是我们可以执行一些不知道Angular的代码,这些代码可能仍然会改变作用域上的东西。如果我们把代码包装在美元应用中,它会负责调用美元摘要()。美元应用()的粗略实现。

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

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

对于前。

1)

// If $scope have some value in Controller.$scope.name = "Peter";
// HTML<div> \{\{ name }} </div>

业务处理

Peter

您可以在ng-model中绑定数据,例如:-
(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>

在这里,如果你想在控制器中使用输入name,那么,

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

ng-model绑定我们的视图并在表达式\{\{ }}中呈现它。
ng-model是在视图中显示给用户并与用户交互的数据。
所以在AngularJs中绑定数据很容易。

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

  1. 单向数据绑定是一种从数据模型中获取值并插入超文本标记语言元素的方法。无法从视图更新模型。它用于经典模板系统。这些系统仅向一个方向绑定数据。

  2. Angular应用程序中的数据绑定是模型和视图组件之间数据的自动同步。

数据绑定允许您将模型视为应用程序中的单一真实来源。视图始终是模型的投影。如果模型发生变化,视图会反映变化,反之亦然。

数据绑定方式

什么是数据绑定?

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

这怎么可能?

短答案:#36825;的帮助下,

描述:Angular js在范围模型上设置观察者,如果模型发生变化,它会触发侦听器函数。

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

//使用新值更新代码

});

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

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

摘要周期被称为自动触发,作为内置指令/服务(如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()

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

上述功能在浏览器IE中也适用于上述版本,只需确保您的应用程序是角度js应用程序,这意味着您正在使用脚本标记中引用的angularjs框架脚本文件。

谢谢。