很长一段时间我一直想知道这个问题:在使用AngularJS时,我应该直接使用视图上的模型对象属性,还是可以使用函数来获取该属性值?
我一直在Angular做一些小型家庭项目,并且(特别是使用只读指令或控制器)我倾向于创建范围函数来访问和显示视图中的范围对象及其属性值,但是性能方面,这是一个好方法吗?
这种方式似乎更容易维护视图代码,因为如果由于某种原因对象被更改(由于服务器实现或任何其他特殊原因),我只需要更改指令的JS代码,而不是HTML。 这是一个例子:
//this goes inside directive's link function
scope.getPropertyX = function() {
return scope.object.subobject.propX;
}
在视图中我可以简单地做
<span>{{ getPropertyX() }}</span>
而不是
<span>{{ object.subobject.propX }}</span>
在有时它涉及的HTML混乱中,它更难维护。
另一种情况是使用范围函数来测试ng-if上的评估的属性值,而不是直接使用该测试表达式:
scope.testCondition = function() {
return scope.obj.subobj.propX === 1 && scope.obj.subobj.propY === 2 && ...;
}
那么,这种方法有利有弊吗?你能否就这个问题向我提供一些见解?最近一直困扰着我,关于一个沉重的应用程序可能会表现如何,例如一个指令可能变得非常复杂,并且最重要的是可以在ng-repeat中使用,可能会生成数百或数千个实例
谢谢
答案 0 :(得分:8)
我不认为为所有属性创建函数是个好主意。不仅会在每个摘要周期中进行更多的函数调用,以查看函数返回值是否已更改,但它对我来说实际上似乎不太可读和可维护。它可能会为您的控制器添加许多不必要的代码,并使您的控制器成为一个视图模型。你的第二种情况看起来非常精细,复杂的操作看起来就像你希望控制器处理的那样。
至于性能,它根据我写的测试确实有所不同(fiddle,尝试使用jsperf,但每次测试都无法获得不同的设置)。结果几乎快两倍,即使用属性为223,000个摘要/秒,使用吸气剂函数为120,000个摘要/秒。为使用角度$parse
的绑定创建了手表。
要考虑的一件事是继承。如果您取消注释小提琴中的ng-repeat
列表并检查其中一个元素的范围,您可以看到我正在谈论的内容。创建的每个子作用域都继承父作用域的属性。对于对象,它继承了一个引用,因此如果对象上有50个属性,它只会将对象引用值复制到子作用域。如果您有50个手动创建的函数,它会将每个函数复制到它继承的每个子作用域。两种方法的时间都较慢,属性为126,000个摘要/秒,具有吸气功能的时间为80,000个摘要/秒。
我真的不知道如何更容易维护代码,这对我来说似乎更难。如果您不想在服务器对象发生变化时触摸您的HTML,那么在javascript对象中执行此操作可能会更好,而不是将getter函数直接放在您的范围上,即:
$scope.obj = new MyObject(obj); // MyObject class
此外,Angular 2.0将使用Object.observe(),它可以提高性能,但不会使用示波器上的getter函数来提高性能。
看起来这个代码都是为每个函数调用执行的。对于范围本身和返回值,它会针对每个参数调用contextGetter()
,fnGetter()
和ensureSafeFn()
以及ensureSafeObject()
。
return function $parseFunctionCall(scope, locals) {
var context = contextGetter ? contextGetter(scope, locals) : scope;
var fn = fnGetter(scope, locals, context) || noop;
if (args) {
var i = argsFn.length;
while (i--) {
args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText);
}
}
ensureSafeObject(context, expressionText);
ensureSafeFunction(fn, expressionText);
// IE stupidity! (IE doesn't have apply for some native functions)
var v = fn.apply
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
return ensureSafeObject(v, expressionText);
};
},
相比之下,简单的属性被编译成如下:
(function(s,l /**/) {
if(s == null) return undefined;
s=((l&&l.hasOwnProperty("obj"))?l:s).obj;
if(s == null) return undefined;
s=s.subobj;
if(s == null) return undefined;
s=s.A;
return s;
})
答案 1 :(得分:1)
Jason Goemaat做得很好Benchmarking Fiddle。您可以在哪里更改最后一行:
setTimeout(function() { benchmark(1); }, 500);
到
setTimeout(function() { benchmark(0); }, 500);
看到差异。
但他也提出了答案,因为属性是函数调用的两倍。事实上,在我2014年中期的MacBook Pro上,属性快了三倍。
但同样地,调用函数或直接访问属性的区别是0.00001秒 - 或 10微秒。
这意味着,如果您有100个吸气剂,与访问100个属性相比,它们会慢1ms。
只是把事情放在上下文中,感知输入(光子撞击视网膜)到达我们意识所需的时间是300ms(是的,有意识的现实是延迟300ms)。因此,您需要在一个视图上使用30,000个getter才能获得相同的延迟。
在汇编程序时代,软件看起来像这样:
可执行代码行的集合。
但是现在,特别是那些具有最微小复杂程度的软件,其观点是:
通信对象之间的社交互动。
后者更关心的是如何通过通信对象建立行为,而不是实际的低级实现。反过来,通信由接口授予,通常使用查询或命令原则来实现。重要的是,协作对象的接口(或合同),而不是低级实现。
通过直接检查属性,您将绕过其接口触及对象的内部,从而将调用者耦合到被调用者。
显然,有了吸气剂,你可能会正确地问“有什么意义”。好吧,考虑这些不会影响界面的植入变化:
因此,即使是看似简单的实现也可能会改变,如果使用getter只需要单个更改,那么不需要进行很多更改。
所以我认为除非你有一个优化的配置文件,否则你应该使用getter。
答案 2 :(得分:0)
无论你在{{}}
中放置什么,都会被评估很多。必须对每个摘要周期进行评估,以便了解值是否发生了变化。因此,一个非常重要的角度规则是确保您在任何$watch
es中都没有昂贵的操作,包括通过{{}}
注册的操作。
现在直接引用属性或者有一个函数之间的区别除了将它返回给我之外,似乎negligible。 (如果我错了,请纠正我)
因此,只要您的功能没有执行昂贵的操作,我认为这实际上是个人偏好的问题。