首页 文章

数据绑定如何在AngularJS中运行?

提问于
浏览
1844

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

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

我发现有JavaScript watchers可以做这项工作 . 但Internet Explorer 6Internet Explorer 7不支持它们 . 那么AngularJS如何知道我改变了例如以下内容并在视图上反映了这一变化?

myobject.myproperty="new value";

14 回答

  • 78

    碰巧我需要将人的数据模型与表单链接起来,我所做的是将数据与表单直接映射 .

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

    $scope.model.people.name
    

    表格的控制输入:

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

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

    我通过模型的示例是从服务器数据更新的,当您根据书面加载要求邮政编码和邮政编码时,与该视图关联的殖民地和城市列表,默认情况下为用户设置第一个值 . 而且我工作得很好,发生了什么,是 angularJS 有时需要几秒钟来刷新模型,为此你可以在显示数据的同时放置一个微调器 .

  • 20
    • 单向数据绑定是一种从数据模型中获取值并插入HTML元素的方法 . 无法从视图更新模型 . 它用于经典模板系统 . 这些系统仅在一个方向上绑定数据 .

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

    通过数据绑定,您可以将模型视为应用程序中的单一事实来源 . 该视图始终是模型的投影 . 如果模型已更改,则视图会反映更改,反之亦然 .

  • 7

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

    • 通过将函数(将要监视的内容返回)传递给 $watch 方法来监视项目 .

    • 必须在 $apply 方法包装的代码块内对已观看项目进行更改 .

    • $apply 的末尾调用了 $digest 方法,该方法遍历每个监视并检查自上次 $digest 运行以来它们是否发生了变化 .

    • 如果发现任何更改,则再次调用摘要,直到所有更改都稳定下来 .

    在正常开发中,HTML中的数据绑定语法告诉AngularJS编译器为您创建监视,控制器方法已在 $apply 内运行 . 所以对应用程序开发人员来说,它是透明的 .

  • 3

    我有点想知道这件事 . 没有setter AngularJS 如何注意到 $scope 对象的变化?它是否会轮询他们?

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

    当文档谈到必须为 AngularJS 之外的代码手动调用 $apply 时,它's talking about code which, when run, doesn'来自调用堆栈中的 AngularJS 本身 .

  • 5

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

    $watch() : -

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

    $digest() -

    此函数遍历$ scope对象中的所有监视及其子$ scope对象(如果有) . 当$ digest()遍历监视时,它会检查表达式的值是否已更改 . 如果值已更改,AngularJS将使用新值和旧值调用侦听器 . 只要AngularJS认为有必要,就会调用$ digest()函数 . 例如,在单击按钮之后,或在AJAX调用之后 . 在某些情况下,AngularJS不会为您调用$ digest()函数 . 在这种情况下,你必须自己调用它 .

    $apply() -

    Angular会自动神奇地更新AngularJS上下文中的模型更改 . 当您更改Angular上下文之外的任何模型(如浏览器DOM事件,setTimeout,XHR或第三方库)时,您需要通过手动调用$ apply()来通知Angular更改 . 当$ apply()函数调用完成AngularJS时在内部调用$ digest(),因此所有数据绑定都会更新 .

  • 4

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

    Angular为这些 Spectator 添加了观察者

    {{表达式}} - 在您的模板中(以及其他有表达式的地方)或我们定义ng-model时 . $ scope . $ watch('expression / function') - 在你的JavaScript中,我们可以附加一个范围对象来观察角度 .

    $watch 函数有三个参数:

    第一个是观察者函数,它只返回对象,或者我们可以添加一个表达式 . 第二个是侦听器函数,当对象发生更改时将调用该函数 . DOM更改等所有内容都将在此函数中实现 . 第三个是可选参数,它接受一个布尔值 . 如果它是真的,有角度的深度观察对象&如果它的假角度只是做一个参考观察对象 . $ 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();
           }
    };
    
  • 4

    用图片解释:

    数据绑定需要映射

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

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

    事件导致$ digest()

    Angular维护每个绑定的 oldValuenewValue . 在每个Angular事件之后,着名的 $digest() 循环将检查WatchList以查看是否有更改 . 这些Angular事件是 ng-clickng-change$http 已完成......只要 oldValuenewValue 不同, $digest() 就会循环 .

    在上图中,它会注意到data-ref1和data-ref2已经改变 .

    结论

    它有点像鸡蛋和鸡肉 . 你永远不知道是谁开始,但希望它能按预期大部分时间工作 .

    另一点是,您可以轻松理解简单绑定对内存和CPU的影响 . 希望桌面足够肥胖来处理这个问题 . 手机并不那么强大 .

  • 313

    通过脏检查$ scope对象

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

    每个观察者都是 object ,其中包含其他内容

    • 观察者正在监视的表达式 . 这可能只是一个 attribute 名称,或者更复杂的名称 .

    • 表达式的最后一个已知值 . 可以根据表达式的当前计算值来检查 . 如果值不同,观察者将触发该功能并将 $scope 标记为脏 .

    • 如果观察者脏了将执行的功能 .

    如何定义观察者

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

    • 您可以在 $scope 上明确 $watch attribute .
    $scope.$watch('person.username', validateUnique);
    
    • 您可以在模板中放置 {{}} 插值(将在当前 $scope 上为您创建观察器) .
    <p>username: {{person.username}}</p>
    
    • 您可以要求 ng-model 等指令为您定义观察者 .
    <input ng-model="person.username" />
    

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

    当我们通过正常通道(ng-model,ng-repeat等)与AngularJS交互时,指令将触发摘要周期 .

    摘要周期是 depth-first traversal of $scope and all its children . 对于每个 $scope object ,我们迭代通过 $$watchers array 并评估所有表达式 . 如果新表达式值与上一个已知值不同,则调用观察者的函数 . 此函数可能会重新编译DOM的一部分,重新计算 $scope 上的值,触发 AJAX request ,您需要它做的任何事情 .

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

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

    如果触发了观察者,则应用程序知道某些内容已更改,并且 $scope 被标记为脏 .

    Watcher函数可以更改 $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名 Spectator 的数字,之后你的应用程序在旧版浏览器上会感觉很慢 .

    例如,如果您在大型 JSON arrayng-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>
    
  • 148

    data binding:

    What is data binding?

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

    How is it possible?

    Short answer : 在摘要周期的帮助下 .

    Description : Angular js在范围模型上设置观察者,如果模型发生更改,则会触发侦听器功能 .

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

    // Dom使用新值更新代码

    });

    So When and How is the watcher function called?

    Watcher函数被称为摘要周期的一部分 .

    摘要周期被称为自动触发,作为在ng-model,ng-bind,$ timeout,ng-click等指令/服务中构建的角度js的一部分,可以触发摘要周期 .

    摘要循环功能:

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

    $rootScope.$apply()

    注意:$ apply()等于$ rootScope . $ digest()这意味着脏检查从根或顶部或父作用域开始向下到角度js应用程序中的所有子$作用域 .

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

    谢谢 .

  • 59

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

    HTML Code

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

    AngularJS Code

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

    正如您在上面的示例中所看到的, AngularJS 使用 ng-model 来监听和观察HTML元素上发生的情况,尤其是在 input 字段上 . 当事情发生时,做一些事情 . 在我们的例子中, ng-model 使用小胡子符号 {{}} 绑定到我们的视图 . 输入字段内输入的内容将立即显示在屏幕上 . 这就是数据绑定的优点,使用最简单的AngularJS .

    希望这可以帮助 .

    请参阅Codepen上的工作示例

  • 6

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

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

    将脏检查(AngularJS)与更改侦听器(KnockoutJSBackbone.js)进行对比:虽然脏检查看似简单,甚至效率低下(我稍后会解决),但事实证明它在语义上始终是正确的,而更改听众有很多奇怪的角落情况,并需要依赖跟踪之类的东西,以使其在语义上更正确 . KnockoutJS依赖关系跟踪是AngularJS没有的问题的一个聪明特征 .

    更改侦听器的问题:

    • 语法非常糟糕,因为浏览器本身不支持它 . 是的,有代理,但在所有情况下它们在语义上都不正确,当然在旧浏览器上没有代理 . 底线是脏检查允许你做POJO,而KnockoutJS和Backbone.js强迫你从他们的类继承,并通过访问器访问你的数据 .

    • 改变合并 . 假设您有一系列项目 . 假设您要将项目添加到数组中,因为您要循环添加,每次添加时都会在更改时触发事件,从而呈现UI . 这对性能非常不利 . 你想要的是最后只更新一次UI . 变更事件太精细了 .

    • 更改侦听器会立即触发setter,这是一个问题,因为更改侦听器可以进一步更改数据,从而触发更多更改事件 . 这很糟糕,因为在您的堆栈上,您可能会同时发生多个更改事件 . 假设您有两个阵列需要保持同步,无论出于何种原因 . 您只能添加到其中一个,但每次添加时都会触发一个更改事件,该事件现在具有不一致的世界视图 . 这是一个非常类似于线程锁定的问题,JavaScript避免了这个问题,因为每个回调都是独占执行完成的 . 更改事件打破了这一点,因为setter可能会产生影响深远的后果,这些后果不是非常明显的,而是非常明显的,这会再次产生线程问题 . 事实证明,你想要做的是延迟监听器执行,并保证一次只运行一个监听器,因此任何代码都可以自由地更改数据,并且它知道在执行此操作时没有其他代码运行 .

    性能怎么样?

    所以看起来我们很慢,因为脏检查是低效的 . 这是我们需要查看实数而不仅仅是理论参数的地方,但首先让我们定义一些约束 .

    人类是:

    • 慢 - 任何比50毫秒快的东西都是人类察觉不到的,因此可以被认为是"instant" .

    • 有限 - 无论如何你都可以't really show more than about 2000 pieces of information to a human on a single page. Anything more than that is really bad UI, and humans can'处理它 .

    所以真正的问题是:你可以在50毫秒内对浏览器进行多少次比较?这是一个很难回答的问题,因为有很多因素可以发挥作用,但这是一个测试案例:http://jsperf.com/angularjs-digest/6创造了10,000名观察者 . 在现代浏览器上,这需要不到6毫秒 . 在Internet Explorer 8上大约需要40毫秒 . 正如您所看到的,即使在速度较慢的浏览器上,这也不是问题 . 有一点需要注意:比较需要很简单才能适应时间限制...不幸的是,在AngularJS中添加慢速比较太简单了,所以当你不知道你是什么时很容易构建慢速应用程序是做 . 但我们希望通过提供一个仪器模块来获得答案,该模块将向您展示哪些是缓慢的比较 .

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

  • 2684

    Misko已经对数据绑定的工作原理进行了很好的描述,但我想在数据绑定的基础上添加我对性能问题的看法 .

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

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

    或者考虑企业Web应用程序中的数据网格 . 每页50行并不合理,每行可以有10-20列 . 如果你 Build 这个如果使用ng-repeats,和/或在某些使用某些绑定的单元格中有信息,则可能仅使用此网格接近2000个绑定 .

    我在使用AngularJS时发现这是一个 huge 问题,到目前为止我能找到的唯一解决方案是构建小部件而不使用双向绑定,而不是使用ngOnce,取消注册观察者和类似技巧,或构造使用jQuery和DOM操作构建DOM的指令 . 我觉得这首先打败了使用Angular的目的 .

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

    TL;DR
    数据绑定可能会导致复杂页面出现性能问题 .

  • 14

    AngularJs支持 Two way data-binding .
    意味着您可以访问数据 View -> ControllerController -> View

    For Ex.

    1)

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

    O/P

    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中绑定数据很容易 .

  • 31

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

相关问题