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

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

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

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

myobject.myproperty="new value";

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

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

为了比较脏检查(AngularJS)和更改监听器(KnockoutJS和Backbone.js):尽管脏检查可能看起来很简单,甚至效率低下(我将在后面解决),但事实证明,它始终在语义上是正确的,而更改监听器有很多怪异的角落案例,并且需要诸如依赖关系跟踪之类的东西来使其更加符合语义。 KnockoutJS依赖关系跟踪是AngularJS没有的一个聪明的特性。

更改监听器的问题:

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

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

    人类是:

  • 慢 - 任何超过50毫秒的速度都不会被人看到,因此可以被视为“即时”。

  • 有限 - 您无法在单个页面上向人显示超过2000条信息。 除此之外的任何东西都是非常糟糕的用户界面,人类无法处理这一点。

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

    事实证明,视频游戏和GPU使用脏检查方法,特别是因为它是一致的。 只要它们超过显示器刷新频率(通常为50-60 Hz,或每16.6-20毫秒),任何超过这个性能的结果都是浪费,因此与提高FPS相比,您最好绘制更多的内容。


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

    正如米斯科所说,2000年左右的绑定就是你开始看到问题的地方,但是你不应该在页面上有超过2000条信息。 这可能是事实,但并不是每个数据绑定对用户都是可见的。 一旦你开始用双向绑定构建任何类型的窗口小部件或数据网格,你就可以轻松地打2000个绑定,而不会产生不好的ux。

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

    或者考虑企业Web应用程序中的数据网格。 每页50行不是不合理的,每行可能有10-20列。 如果你用ng-repeats构建这个,并且/或者在一些使用绑定的单元格中有信息,那么你可能会单独接近2000个绑定。

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

    我很乐意听取其他处理方法的建议,但也许我应该写下自己的问题。 我想在评论中提到这一点,但事实证明,这样做太长了......

    TL; DR
    数据绑定可能会导致复杂页面上的性能问题。


    通过脏检查$scope对象

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

    每个观察者都是一个包含其他事物的object

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

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

  • 您可以显式$watch $scope一个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交互时,摘要循环将由指令触发。

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

    遍历每个范围,并对每个手表表达式进行评估并根据最后一个值进行检查。

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

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

    观察者函数可以改变$scope$scope parent $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 array ng-repeat ,则此限制很容易达到。 您可以使用诸如一次性绑定之类的功能来减轻编译模板而不创建观察者。

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

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

    如果你的数据很少变化,你可以使用:: syntax绑定一次,如下所示:

    <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>
    
    链接地址: http://www.djcxy.com/p/535.html

    上一篇: How does data binding work in AngularJS?

    下一篇: jQuery scroll to element