关于javascript:AngularJS中’this’ 和 $scope 如何工作

How does data binding work in AngularJS?

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

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

我发现有javascript观察程序可以完成这项工作。但Internet Explorer 6和Internet Explorer 7不支持这些功能。那么AngularJS如何知道我做了更改,例如下面的更改,并将此更改反映在视图上?

1
myobject.myproperty="new value";


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

当从非AngularJS世界过渡到AngularJS世界时,您所调用的$apply()方法称为$digest()。摘要只是一个简单的陈旧的检查。它适用于所有浏览器,完全可以预测。好的。

对比脏检查(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/>数据绑定可能会导致复杂页面出现性能问题。


通过脏检查$scope对象

Angular在$scope对象中保持一个简单的array观察者。如果你检查任何一个$scope,你会发现它含有一个名为$$watchersarray。好的。

每个观察者都是一个包含其他内容的object。好的。

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

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

    • 您可以在$scope上显式地使用$watchattribute。好的。

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

      1
      2
      3
      <p>
      username: {{person.username}}
      </p>Ok.
    • 您可以请求诸如ng-model之类的指令为您定义观察者。好的。

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

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

    当我们通过正常通道(ng模型、ng重复等)与angularjs交互时,指令将触发一个消化循环。好的。

    摘要循环是$scope及其所有子代的深度优先遍历。对于每个$scopeobject,我们迭代其$$watchersarray并计算所有表达式。如果新的表达式值与上一个已知值不同,则调用观察程序的函数。此函数可以重新编译DOM的一部分,在$scope上重新计算值,触发AJAXrequest,任何您需要它做的事情。好的。

    遍历每个范围,并根据最后一个值对每个监视表达式进行计算和检查。好的。如果触发了观察程序,则$scope是脏的

    如果一个观察者被触发,应用程序就会知道发生了什么变化,并且$scope被标记为脏的。好的。

    观察程序函数可以更改$scope或父级$scope上的其他属性。如果一个$watcher功能被触发,我们不能保证其他$scope功能仍然是干净的,因此我们再次执行整个消化循环。好的。

    这是因为AngularJS具有双向绑定,所以可以将数据传回$scope树。我们可以改变已经被消化的更高的$scope的值。也许我们改变了$rootScope的值。好的。如果$digest是脏的,我们再次执行整个$digest循环。

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

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

    1
    10 $digest() iterations reached. Aborting!

    摘要在机器上很难,但在开发人员上很容易

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

    从机器的角度来看,虽然这是非常低效的,如果我们创建了太多的观察者,将会减慢我们的应用程序的速度。Misko引用了大约4000个观察者的数据,在你的应用程序在老版本的浏览器上会感觉缓慢之前。好的。

    例如,如果您在一个大的JSONarray上使用ng-repeat,则很容易达到此限制。您可以使用诸如一次性绑定之类的功能来编译模板,而不必创建观察程序来缓解这种情况。好的。如何避免创建过多的观察者

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

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

    1
    2
    3
    <p>
    {{::person.username}}
    </p>Ok.

    或好的。

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

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

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

    1
      {{::person.username}}

    好啊。


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

  • 通过传递函数(返回注意)使用$watch方法。
  • 对被监视项的更改必须在代码块内进行用$apply方法包装。
  • $apply的末尾,调用$digest方法,如下所示:通过每一个手表和检查看它们是否在上次$digest运行。
  • 如果发现任何更改,则再次调用摘要,直到所有更改稳定为止。
  • 在正常的开发中,HTML中的数据绑定语法告诉AngularJS编译器为您创建监视,控制器方法已经在$apply中运行。所以对于应用程序开发人员来说,它是透明的。


    我自己想了一会儿。没有设置者,AngularJS如何通知$scope对象的更改?它调查他们吗?

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

    当文档谈到必须手动调用$apply以获取AngularJS之外的代码时,它所说的代码在运行时并不源于调用堆栈中的AngularJS


    用图片解释:

    数据绑定需要映射

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

    enter image description here

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

    事件导致$Digest()。

    enter image description here

    角形保持每个结合的oldValuenewValue。在每一个角度事件之后,著名的$digest()循环将检查观察列表,看是否有什么变化。这些角事件是ng-clickng-change$http完成的…只要oldValuenewValue不同,$digest()就会循环。

    在上一张图片中,它将注意到data-ref1和data-ref2已更改。

    结论

    有点像鸡蛋和鸡肉。你永远不知道是谁开始的,但希望它能像预期的那样在大多数时候发挥作用。

    另一点是,您可以轻松理解简单绑定对内存和CPU的影响。希望台式机足够胖来处理这个问题。手机不那么结实。


    显然,没有对Scope的定期检查,它所附对象是否有任何变化。并不是所有附加到作用域的对象都被监视。Scope原型维护一个$watchers。当调用$digest时,Scope只遍历此$$watchers

    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">

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

    我传递模型的一个例子是从服务器数据更新,当您请求基于写入的邮政编码和邮政编码时,加载与该视图相关联的殖民地和城市列表,并且默认情况下与用户设置第一个值。我做得很好,实际情况是,angularJS有时需要几秒钟来刷新模型,为此,您可以在显示数据时放置一个旋转器。


  • 单向数据绑定是一种从数据模型中获取值并将其插入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

    您可以在ng-model中绑定数据,如下所示:2)

    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>

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

    1
    2
    3
    4
    5
    $scope.name = {};

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

    ng-model结合了我们的观点,并以{{ }}的形式呈现出来。ng-model是在视图中显示给用户并与用户交互的数据。所以很容易在AngularJS中绑定数据。


    下面是使用输入字段与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使用ng-model来监听和监视HTML元素上发生的事情,特别是在input字段上。当有事情发生时,做点什么。在我们的例子中,使用胡子符号{{}}ng-model与我们的观点是绑定的。输入字段中键入的内容将立即显示在屏幕上。这就是数据绑定的好处,以最简单的形式使用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$rootScope.$apply()

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

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

    谢谢您。