AngularJS框架中的数据绑定是如何工作的?
我还没有在他们的网站上找到技术细节。当数据从视图传播到模型时,它是如何工作的,这或多或少是清楚的。但是AngularJS如何在没有setter和getter的情况下跟踪模型财产的更改?
我发现有JavaScript观察者可能会做这项工作。但Internet Explorer 6和Internet Explorer 7不支持它们。那么AngularJS如何知道我更改了例如以下内容,并将此更改反映在视图上?
myobject.myproperty="new value";
AngularJS记住该值并将其与以前的值进行比较。这是基本的脏检查。如果值发生变化,则触发变化事件。
$apply()方法调用$digital(),它是当您从非AngularJS世界转换到AngularJS时调用的方法。摘要只是一个简单的旧检查。它适用于所有浏览器,完全可以预测。
对比脏检查(AngularJS)和更改侦听器(KnockoutJS和Backbone.js):虽然脏检查可能看起来很简单,甚至效率很低(我稍后会讨论),但事实证明它始终在语义上是正确的,而更改侦听器有很多奇怪的角落情况,需要依赖项跟踪之类的东西来使其更加语义正确。KnockoutJS依赖跟踪是解决AngularJS没有的问题的一个聪明功能。
更改侦听器的问题:
这种语法非常糟糕,因为浏览器本身不支持它。是的,有代理,但它们在所有情况下都不是语义正确的,当然在旧浏览器上也没有代理。底线是,脏检查允许您执行POJO,而KnockoutJS和Backbone.js强制您从它们的类继承,并通过访问器访问数据。更改合并。假设您有一个项目数组。假设您想将项添加到数组中,因为您正在循环添加,每次添加时都会触发更改事件,这就是呈现UI。这对性能非常不利。您需要的是在最后只更新一次UI。更改事件过于精细。更改侦听器会立即触发setter,这是一个问题,因为更改侦听器可以进一步更改数据,从而触发更多的更改事件。这很糟糕,因为在堆栈上,可能会同时发生多个更改事件。假设您有两个数组,无论出于何种原因,它们都需要保持同步。您只能添加其中一个或另一个,但每次添加时都会引发一个更改事件,这会导致对世界的看法不一致。这是一个非常类似于线程锁定的问题,JavaScript避免了这个问题,因为每个回调都以独占方式执行,直到完成。更改事件打破了这一点,因为设置器可能会产生深远的后果,而这些后果不是故意的,也不是显而易见的,这会再次导致线程问题。事实证明,您想要做的是延迟侦听器的执行,并保证一次只运行一个侦听器,因此任何代码都可以自由更改数据,并且它知道在执行此操作时没有其他代码运行。
性能如何?
因此,我们似乎很慢,因为脏检查效率很低。这就是我们需要研究实数的地方,而不仅仅是理论论证,但首先我们来定义一些约束条件。
人类是:
慢-任何速度超过50毫秒的东西都是人类无法察觉的,因此可以被视为“瞬间”。有限-你不能在一个页面上向人类显示超过2000条信息。除此之外的任何东西都是非常糟糕的UI,人类无论如何都无法处理它。
所以真正的问题是:在50毫秒内,你可以在浏览器上进行多少次比较?这是一个很难回答的问题,因为许多因素都在起作用,但这里有一个测试案例:http://jsperf.com/angularjs-digest/6这创造了10000名观众。在现代浏览器上,这需要不到6毫秒的时间。在Internet Explorer 8上,大约需要40毫秒。正如你所看到的,即使是在现在速度较慢的浏览器上,这也不是问题。有一个警告:比较需要简单,以符合时间限制。。。不幸的是,在AngularJS中添加缓慢的比较太容易了,所以当你不知道自己在做什么时,很容易构建缓慢的应用程序。但我们希望通过提供一个检测模块来得到答案,它将向您显示哪些是比较缓慢的。
事实证明,视频游戏和GPU使用脏检查方法,特别是因为它是一致的。只要它们超过了监视器刷新率(通常为50-60 Hz,或每16.6-20 ms),任何超过这个速度的性能都是浪费,所以你最好画更多的东西,而不是提高FPS。
用图片解释:
数据绑定需要映射
作用域中的引用与模板中的引用不完全相同。当您数据绑定两个对象时,需要第三个对象侦听第一个对象并修改另一个对象。
在这里,当您修改<input>时,您会触摸data-ref3。经典的数据绑定机制将改变data-ref4。那么其他{{数据}}表达式将如何移动?
事件导致$digest()
Angular维护每个绑定的oldValue和newValue。在每一个Angular事件之后,著名的$digest()循环都会检查WatchList以查看是否发生了变化。这些Angular事件是ng单击、ng更改、$http完成。。。只要任何oldValue与newValue不同,$digest()就会循环。
在上一张图片中,它将注意到data-ref1和data-ref2已更改。
结论
这有点像鸡蛋和鸡肉。你永远不知道是谁开始,但希望大多数时候都能像预期的那样。
另一点是,您可以很容易地理解简单绑定对内存和CPU的影响。希望台式机足够胖,可以处理这个问题。手机没有那么强。
AngularJS借助三个强大的函数来处理数据绑定机制:$watch()、$digital()和$apply()。大多数时候AngularJS都会调用$scope$watch()和$scope$digest(),但是在某些情况下,您可能需要手动调用这些函数来更新新值。
$watch():-
此函数用于观察$scope上变量的变化。它接受三个参数:表达式、监听器和相等对象,其中监听器和相等对象是可选参数。
$digest()-
此函数迭代$scope对象中的所有手表,及其子$scope对象(如果有)。当$digest()迭代时在手表上,它检查表达式的值是否改变。如果值已更改,AngularJS将使用新值和旧值。调用$digest()函数每当AngularJS认为有必要时。例如,在按钮之后或者在AJAX调用之后。在某些情况下,AngularJS不会为您调用$digest()函数。在这种情况下,你必须你自己说吧。
$apply()-
Angular确实会自动神奇地更新那些在AngularJS上下文中。当您在Angular上下文(如浏览器DOM事件、setTimeout、XHR或第三个party库),则需要通过手动调用$apply()。当$apply()函数调用完成时AngularJS在内部调用$digital(),因此所有数据绑定都是更新。