AngularJS:何时将变量传递给函数$scope

AngularJS: When to pass $scope variable to function

本文关键字:函数 scope 何时 变量 AngularJS      更新时间:2023-09-26

我正在使用TodoMVC应用程序来改善AngularJS框架。在索引中.html在第 14-16 行,您会看到以下内容:

<form id="todo-form" ng-submit="addTodo()">
    <input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus>
</form>

请注意 ng-submit 指令如何调用 addTodo() 函数,而无需将 newTodo 模型作为参数传递。

不久之后,我在第 19 行的同一文件中遇到了以下代码:

<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">

你可以看到作者这次决定将 allChecked 模型传递给 markAll() 函数。如果我理解正确,他们可以在控制器内部引用 $scope.allChecked,而不是将其传入。

为什么在同一文件中使用两种不同的方法?在某些情况下,一种方法更好吗?这是不一致的情况还是使用了更深层次的逻辑?

我宁愿总是将参数传递给函数:

    函数
  • 需要哪些参数更清楚。
  • 单元测试更容易,因为所有参数都注入到函数中。(适用于单元测试)

请考虑以下情况:

$scope.addToDo = function(){
   //This declaration is not clear what parameters the function expects.
   if ($scope.parameter1){
      //do something with parameter2
   }    
}

更糟糕的是:

$scope.addToDo = function(){
    //This declaration is not clear what parameters the function expects.
    if ($scope.someobject.parameter1){ //worse
    }    
}

由于作用域继承parameter2可能来自父作用域,因此访问函数内部的parameter2会创建紧密耦合,在尝试对该函数进行单元测试时也会导致麻烦。

如果我像这样定义函数:

//It's clearer that the function expects parameter1, parameter2
$scope.addToDo = function(parameter1, parameter2){
   if (parameter1){
      //do something with parameter2
   }    
}

如果parameter2是从父范围继承的,您仍然可以从视图中传入它。执行单元测试时,很容易传递所有参数。

如果你曾经使用过 ASP.NET MVC,你会注意到类似的东西:框架试图将参数注入到action函数中,而不是直接从RequestHttpContext对象访问它

万一其他人提到喜欢与ng-repeat合作,这也很好

在我看来,角度中的控制器和模型并没有完全明确地分开。$scope对象看起来像我们的模型,具有属性和方法(模型还包含逻辑)。来自OOP背景的人会认为:我们只传入不属于对象的参数。就像类 Person 已经有 hands 一样,我们不需要为每个对象方法传入 hands。像这样的示例代码:

//assume that parameter1 belongs to $scope, parameter2 is inherited from parent scope.
    $scope.addToDo = function(parameter2){ 
        if ($scope.parameter1){ //parameter1 could be accessed directly as it belongs to object, parameter2 should be passed in as parameter.
            //do something with parameter2
        }   
    }

这个答案有两个部分,第一部分是回答哪一个是更好的选择,另一部分是它们都不是一个好的选择!


哪一个是正确的?

这个是:

$scope.addToDo = function(params1, ...) {
    alert(params1);
}

为什么?因为 A - 它是可测试的。即使您不编写测试,这一点也很重要,因为从长远来看,可测试的代码几乎总是更具可读性和可维护性。

它也因为 B 而更好 - 当涉及到调用者时,它是不可知的。此函数可以被任意数量的不同控制器/服务/等重用,因为它不依赖于作用域的存在或该作用域的结构。

改为执行此操作时:

$scope.addToDo = function() {
    alert($scope.params1);
}

A 和 B 都失败。它本身不容易测试,并且不容易重用,因为您使用它的范围可能格式不同。

编辑:如果您正在做与您的特定范围密切相关的事情并从模板运行函数,那么您可能会遇到试图使其可重用没有意义的情况。该函数根本不是通用的。在这种情况下,不要为此烦恼,某些功能无法重用。查看我写的内容作为您的默认模式,但请记住,在某些情况下它不适合。


为什么两者都错了?

因为作为一般规则,您不应该在控制器中执行逻辑,这是服务的工作。控制器可以使用服务并调用函数或在模型中公开它,但它不应定义它。

为什么这很重要?因为它再次使重用该功能变得容易。在一个控制器中定义的函数不能在另一个控制器中重用,除非对在 HTML 中调用控制器的方式施加限制。服务中定义的函数可以在您喜欢的任何位置注入和重用。

但我不需要重用该功能! - 是的,你愿意!也许不是现在,也许永远不会用于这个特定的功能,但迟早你会想要重用一个你确信你永远不需要重用的功能。然后,您将不得不返工已经忘记一半的代码,这总是需要额外的时间。

最好从一开始就正确执行此操作,并将所有可以的逻辑移动到服务中。这样,如果你在其他地方(甚至在另一个项目中)需要它们,你可以抓住它并使用它,而不必重写它以适应你当前的范围结构。

当然,服务不知道您的范围,因此您被迫使用第一个版本。奖金!并且不要陷入将整个范围传递给服务的诱惑,这永远不会有好结果:-)

所以这是IMO的最佳选择:

app.service('ToDoService', [function(){
    this.addToDo = function(params1, ...){
        alert(params1);
    }
}]);

在控制器内部:

$scope.addToDo = ToDoService.addToDo;

请注意,我写的是"一般规则"。在某些情况下,在控制器本身而不是服务中定义功能是合理的。一个例子是当函数仅与范围特定的事情相关时,例如以某种方式切换控制器中的状态。在服务中没有真正的方法可以在不让事情变得奇怪的情况下做到这一点。

但听起来这里的情况并非如此。

The Zen of Angular 建议:

在模板中将范围视为只读在控制器中将作用域视为仅写入

遵循此原则,应始终使用模板中的参数调用函数显式。

但是,在您遵循的任何样式中,您都必须注意指令的执行priorities顺序。在您的示例中,使用 ng-modelng-click会使两个指令的执行顺序不明确。解决方案是使用 ng-change ,其中执行顺序很明确:只有在值更改才会执行。

自定义行为方法,如 ng-click、ng-submit 等,允许我们将参数传递给被调用的方法。这很重要,因为我们可能希望传递一些可能无法自由使用的东西,直到控制器中的处理程序。例如,

angular.module('TestApp')
.controller('TestAppController', ['$scope', function($scope) {
    $scope.handler = function(idx) {
        alert('clicked ' + idx.toString());
    };
}]);
<ul>
    <li ng-repeat="item in items">
        <button ng-click="handler($index)">{ item }</button>
        <!-- $index is an iterator automatically available with ngRepeat -->
    </li>
</ul>

在你的第二个例子中,由于allChecked在定义markAll()的同一控制器的范围内,你是绝对正确的,没有必要传递任何东西。我们只是创建另一个副本。

该方法必须简单地重构以使用作用域中可用的内容。

$scope.markAll = function () {
    todos.forEach(function (todo) {
        todo.completed = $scope.allChecked;
    });
};

因此,即使我们可以选择在这些方法中使用参数,它们也只是在某些时候是必需的。

我认为这只是代码不一致的情况。我之前思考过这个问题,得出了以下结论......

规则:不要将$scope变量传递到$scope函数中。

读取控制器代码应该足以演示组件的功能。视图不应包含任何业务逻辑,而应包含绑定(ng-model、ng-click 等)。如果视图中的某些内容可以通过移动到控制器而更清晰,那就这样吧。

例外:允许条件 ng 类语句(例如 ng-class='{active:$index==item.idx' ) - 将类条件放在控制器中可能非常冗长,并且会用视图中的想法混淆控制器的逻辑。如果是可视属性,请将其保留在视图中。

例外:您正在处理 ng 重复中的项目。例如:

<ul ng-repeat="item in items">
    <li><a ng-click="action(item)"><h1>{{item.heading}}</h1></a></li>
</ul>

我在编写控制器和视图时遵循这些规则,它们似乎有效。希望这有帮助。

也许是为了说明你可以吗?假设它们是相同的控制器,则两者之间没有功能差异。请注意,在某些情况下会生成子作用域,在这种情况下,你将不再具有与控制器相同的作用域。