【问题标题】:AngularJS - Best practice: model properties on view or function calls?AngularJS - 最佳实践:视图或函数调用的模型属性?
【发布时间】:2014-12-30 18:09:13
【问题描述】:

很长一段时间以来,我一直在想这个问题:在使用 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 中使用。

谢谢

【问题讨论】:

  • 一个缺点是有很多摘要循环,因此最小化函数调用会更高效。如果您将其中一个函数放入 ng-repeat 并在其中添加 console.log,您将看到比您最初预期的更多的日志
  • 我认为为所有属性创建函数并不是一个好主意。不仅每个摘要周期都会进行更多的函数调用,以查看函数返回值是否发生了变化,而且对我来说,它的可读性和可维护性似乎确实降低了。它可能会向您的控制器添加许多不必要的代码,并且有点使您的控制器成为视图模型。您的第二种情况似乎非常好,复杂的操作似乎正是您希望控制器处理的。
  • 这只是让我想知道的事情(即使您的两个 cmets 都完全有效并且我同意它们):这样我们必须在视图代码上维护对象属性(当我们想要显示它们)和控制器代码(例如,当我们想通过比较这些属性来有条件地在视图上显示内容时)。
  • @JasonGoemaat 直接使用属性和使用简单的getter函数真的有区别吗?每个摘要周期都将评估任何一个。函数调用本身是否真的有足够的开销会导致性能显着差异?还是我对某事有错误的理解?
  • 提前考虑,我看到 Angular 2.0 将合并 Object.observe(),因此可能会产生更大的影响。

标签: javascript angularjs model-view-controller angularjs-directive


【解决方案1】:

无论您在{{}} 中输入的内容,都会被大量评估。必须在每个摘要循环中对其进行评估,以了解该值是否发生了变化。因此,Angular 的一个非常重要的规则是确保您在任何 $watches 中都没有昂贵的操作,包括那些通过 {{}} 注册的操作。

现在,在我看来,直接引用属性或让函数只返回它的区别似乎是negligible。 (如有错误请指正)

所以,只要你的函数不执行昂贵的操作,我认为这真的是个人喜好问题。

【讨论】:

  • 是的,特别是这个指令(可以重复数百次)正在处理一个具有大约 20 个属性的对象(其中一些是数组,其他字符串,其他是对象本身)。但大部分都是直接显示,没有任何数据处理;其他人(只有少数人)有一些额外的处理,有时会结合起来测试 ng-ifs 等的条件。现在我对你的回答和之前的 cmets 有点矛盾,因为在我看来他们指向不同的方向。
【解决方案2】:

我认为为所有属性创建函数并不是一个好主意。不仅每个摘要周期都会进行更多的函数调用,以查看函数返回值是否发生了变化,而且对我来说,它的可读性和可维护性似乎确实降低了。它可能会向您的控制器添加许多不必要的代码,并且有点使您的控制器成为视图模型。您的第二种情况似乎很好,复杂的操作似乎正是您希望控制器处理的。

至于性能,根据我编写的测试(fiddle,尝试使用 jsperf 但每次测试无法获得不同的设置),它确实会有所不同。结果几乎快了两倍,即使用属性为 223,000 个摘要/秒,而使用 getter 函数为 120,000 个摘要/秒。手表是为使用 Angular 的 $parse 的绑定创建的。

要考虑的一件事是继承。如果您取消注释小提琴中的ng-repeat 列表并检查其中一个元素的范围,您可以看到我在说什么。创建的每个子范围都继承父范围的属性。对于对象,它继承一个引用,因此如果您的对象上有 50 个属性,它只会将对象引用值复制到子范围。如果您有 50 个手动创建的函数,它会将这些函数中的每一个复制到它所继承的每个子作用域。两种方法的时间都较慢,属性为 126,000 个摘要/秒,而 getter 函数为 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;
})

【讨论】:

  • 好的,我明白了。你的回答确实澄清了一些事情。谢谢。
  • 这是一个很好的答案,尤其是因为它涉及到基准测试。但我相信得出的结论对于现实世界的场景来说是值得商榷的。有关更多信息,请参阅我的答案。
【解决方案3】:

性能方面 - 可能无关紧要

Jason Goemaat 在提供Benchmarking Fiddle 方面做得很好。您可以从哪里更改最后一行:

setTimeout(function() { benchmark(1); }, 500);

setTimeout(function() { benchmark(0); }, 500);

看看有什么不同。

但他也给出了答案,因为属性的速度是函数调用的两倍。事实上,在我 2014 年中期的 MacBook Pro 上,属性速度提高了三倍

但同样,调用函数或直接访问属性之间的差异是 0.00001 秒 - 或 10 微秒

这意味着如果您有 100 个 getter,与访问 100 个属性相比,它们会慢 1ms。

只是把事情放在上下文中,感觉输入(光子撞击视网膜)到达我们的意识所需的时间是 300 毫秒(是的,有意识的现实延迟了 300 毫秒)。因此,您需要在单个视图上使用 30,000 个 getter 才能获得相同的延迟。

明智的代码质量 - 这可能很重要

在汇编器时代,软件是这样的:

可执行代码行的集合。

但如今,特别是对于复杂程度最低的软件,人们的看法是:

通信对象之间的社交互动。

后者更关心如何通过通信对象建立行为,而不是实际的低级实现。反过来,通信由接口授予,这通常使用查询或命令原则来实现。重要的是协作对象的接口(或它们之间的契约),而不是低级实现。

通过直接检查属性,您会绕过对象的接口进入对象的内部,从而将调用者与被调用者耦合。

显然,对于 getter,您可以正确地问“有什么意义”。好吧,考虑一下这些不会影响界面的植入更改:

  • 检查边缘情况,例如是否完全定义了属性。
  • 从返回名字 + 姓氏的 getName() 更改,而不仅仅是 name 属性。
  • 决定将属性存储在可变构造中。

因此,即使是看似简单的实现也可能会发生变化,如果使用 getter 只需要一次更改,则需要进行多次更改。

我投票给吸气剂

所以我认为,除非您有一个用于优化的概要案例,否则您应该使用 getter。

【讨论】:

    猜你喜欢
    • 2011-09-16
    • 2011-04-08
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-26
    • 2021-10-09
    相关资源
    最近更新 更多