ChatGPT解决这个技术问题 Extra ChatGPT

AngularJS 控制器中的“this”与 $scope

"Create Components" section of AngularJS's homepage 中,有以下示例:

controller: function($scope, $element) {
  var panes = $scope.panes = [];
  $scope.select = function(pane) {
    angular.forEach(panes, function(pane) {
      pane.selected = false;
    });
    pane.selected = true;
  }
  this.addPane = function(pane) {
    if (panes.length == 0) $scope.select(pane);
    panes.push(pane);
  }
}

请注意 select 方法是如何添加到 $scope 的,而 addPane 方法是如何添加到 this 的。如果我将其更改为 $scope.addPane,则代码会中断。

该文档说实际上存在差异,但没有提及差异是什么:

以前的 Angular 版本(1.0 RC 之前)允许您将其与 $scope 方法互换使用,但现在不再如此。在范围内定义的方法 this 和 $scope 是可互换的(角度将 this 设置为 $scope),但在控制器构造函数内则不能。

this$scope 如何在 AngularJS 控制器中工作?

我也觉得这很混乱。当视图指定一个控制器时(例如,ng-controller='...'),与该控制器关联的 $scope 似乎也随之而来,因为视图可以访问 $scope 属性。但是当指令'需要另一个控制器(然后在其链接功能中使用它)时,与该另一个控制器关联的 $scope 不会随之而来?
现在是否删除了关于“以前的版本......”的令人困惑的引用?那么也许更新会到位?
对于单元测试,如果你使用 'this' 而不是 '$scope',你不能用模拟范围注入控制器,所以你不能进行单元测试。我认为使用“this”不是一个好习惯。

A
Andrew Tobilko

“this 和 $scope 在 AngularJS 控制器中是如何工作的?”

简短的回答:

this 当调用控制器构造函数时,this 就是控制器。当调用在 $scope 对象上定义的函数时,这是“调用函数时生效的作用域”。这可能(也可能不是!)是定义函数的 $scope。所以,在函数内部,this 和 $scope 可能不一样。

当调用控制器构造函数时,这是控制器。

当调用在 $scope 对象上定义的函数时,这是“调用函数时生效的作用域”。这可能(也可能不是!)是定义函数的 $scope。所以,在函数内部,this 和 $scope 可能不一样。

$scope 每个控制器都有一个关联的 $scope 对象。控制器(构造函数)函数负责在其关联的 $scope 上设置模型属性和函数/行为。只有在这个 $scope 对象(和父范围对象,如果原型继承在起作用)上定义的方法可以从 HTML/view 访问。例如,来自 ng-click、过滤器等。

每个控制器都有一个关联的 $scope 对象。

控制器(构造函数)函数负责在其关联的 $scope 上设置模型属性和函数/行为。

只有在这个 $scope 对象(和父范围对象,如果原型继承在起作用)上定义的方法可以从 HTML/view 访问。例如,来自 ng-click、过滤器等。

长答案:

控制器函数是 JavaScript 构造函数。当构造函数执行时(例如,当加载视图时),this(即“函数上下文”)被设置为控制器对象。所以在“tabs”控制器构造函数中,当addPane函数被创建时

this.addPane = function(pane) { ... }

它是在控制器对象上创建的,而不是在 $scope 上。视图无法看到 addPane 函数——它们只能访问在 $scope 上定义的函数。换句话说,在 HTML 中,这是行不通的:

<a ng-click="addPane(newPane)">won't work</a>

在“tabs”控制器构造函数执行后,我们有以下内容:

https://i.stack.imgur.com/PUMuU.png

黑色虚线表示原型继承——隔离作用域原型继承自 Scope。 (它不会原型继承自在 HTML 中遇到指令的有效范围。)

现在,pane 指令的链接函数想要与 tabs 指令通信(这实际上意味着它需要以某种方式影响 tabs 隔离 $scope)。可以使用事件,但另一种机制是让窗格指令 require 成为选项卡控制器。 (窗格指令似乎没有require选项卡 $scope 的机制。)

所以,这就引出了一个问题:如果我们只能访问选项卡控制器,我们如何访问选项卡隔离 $scope(这是我们真正想要的)?

嗯,红色虚线就是答案。 addPane() 函数的“作用域”(我在这里指的是 JavaScript 的函数作用域/闭包)使函数能够访问选项卡隔离 $scope。即,addPane() 可以访问上图中的“选项卡 IsolateScope”,因为在定义 addPane() 时创建了一个闭包。 (如果我们改为在选项卡 $scope 对象上定义 addPane(),则窗格指令将无法访问此函数,因此它无法与选项卡 $scope 通信。)

要回答您问题的另一部分:how does $scope work in controllers?

在 $scope 上定义的函数中,this 设置为“在哪里/何时调用函数时生效的 $scope”。假设我们有以下 HTML:

<div ng-controller="ParentCtrl">
   <a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
   <div ng-controller="ChildCtrl">
      <a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
   </div>
</div>

ParentCtrl(仅)有

$scope.logThisAndScope = function() {
    console.log(this, $scope)
}

单击第一个链接将显示 this$scope 是相同的,因为“调用函数时生效的范围”是与 ParentCtrl 关联的范围。

单击第二个链接将显示 this$scope 相同,因为“调用函数时的作用域”是与ChildCtrl。所以在这里,this 设置为 ChildCtrl$scope。在方法内部,$scope 仍然是 ParentCtrl 的 $scope。

Fiddle

我尽量不要在 $scope 上定义的函数内使用 this,因为它会混淆哪个 $scope 受到影响,特别是考虑到 ng-repeat、ng-include、ng-switch 和指令都可以创建自己的子范围。


@tamakisquare,我相信您引用的粗体文本适用于调用控制器构造函数时 - 即创建控制器时=与 $scope 关联。当任意 JavaScript 代码调用定义在 $scope 对象上的方法时,它不再适用。
请注意,现在可以通过命名控制器直接在模板中调用 addPane() 函数:“MyController as myctrl”,然后是 myctrl.addPane()。请参阅docs.angularjs.org/guide/concepts#controller
太多固有的复杂性。
这是一个非常有用的答案,但是当我遇到一个实际问题(how to invoke $scope.$apply() in a controller method defined using 'this')时,我无法解决。因此,虽然这仍然是一个有用的答案,但我发现“固有的复杂性”令人困惑。
Javascript - 很多绳子[吊死自己]。
M
Mon Calamari

'addPane' 分配给它的原因是因为 <pane> 指令。

pane 指令执行 require: '^tabs',它将父指令中的标签控制器对象放入链接函数中。

addPane 分配给 this,以便 pane 链接函数可以看到它。那么在pane链接函数中,addPane只是tabs控制器的一个属性,也就是tabsControllerObject.addPane。因此,pane 指令的链接函数可以访问选项卡控制器对象,从而访问 addPane 方法。

我希望我的解释足够清楚......这有点难以解释。


感谢您的解释。文档看起来控制器只是一个设置范围的函数。如果所有操作都发生在范围内,为什么控制器会被视为对象?为什么不直接将父作用域传递给链接函数呢?编辑:为了更好地表达这个问题,如果控制器方法和作用域方法都在同一个数据结构(作用域)上运行,为什么不把它们都放在一个地方呢?
由于希望支持“可重用组件,它不应该意外读取或修改父范围中的数据”,父范围似乎没有传递到 lnk 函数中。但是,如果指令确实想要/需要读取或修改父范围内的某些特定数据(如“窗格”指令),则需要付出一些努力:“要求”所需父范围所在的控制器,然后定义一个该控制器上的方法(使用 'this' 而不是 $scope)来访问特定数据。由于所需的父作用域没有注入到 lnk 函数中,我想这是唯一的方法。
嘿,马克,实际上修改指令的范围更容易。您可以只使用链接功能 jsfiddle.net/TuNyj
感谢@Andy 的提琴。在您的小提琴中,该指令没有创建一个新的范围,所以我可以看到链接函数如何在这里直接访问控制器的范围(因为只有一个范围)。选项卡和窗格指令使用隔离作用域(即,创建的新子作用域在原型上不从父作用域继承)。对于隔离范围的情况,似乎在控制器上定义一个方法(使用“this”)是允许另一个指令(间接)访问另一个(隔离)范围的唯一方法。
r
ruffin

我刚刚阅读了关于两者之间差异的非常有趣的解释,并且越来越倾向于将模型附加到控制器并将控制器别名以将模型绑定到视图。 http://toddmotto.com/digging-into-angulars-controller-as-syntax/ 是文章。

注意:原始链接仍然存在,但格式更改使其难以阅读。在原版中更容易查看。

他没有提到它,但是在定义指令时,如果您需要在多个指令之间共享某些内容并且不想要服务(在某些合法情况下服务很麻烦),那么将数据附加到父指令的控制器。

$scope 服务提供了许多有用的东西,$watch 是最明显的,但如果您只需要将数据绑定到视图,那么在模板中使用普通控制器和“控制器作为”就可以了,而且可以说是更可取的。


o
ofthelit

我建议您阅读以下帖子:AngularJS: "Controller as" or "$scope"?

它很好地描述了使用“Controller as”在“$scope”上公开变量的优点。

我知道您专门询问了方法而不是变量,但我认为最好坚持一种技术并与之保持一致。

所以在我看来,由于帖子中讨论的变量问题,最好只使用“Controller as”技术并将其应用于方法。


S
Sandro

在本课程(https://www.codeschool.com/courses/shaping-up-with-angular-js)中,他们解释了如何使用“this”和许多其他内容。

如果您通过“this”方法向控制器添加方法,则必须在视图中使用控制器名称“点”您的属性或方法来调用它。

例如在视图中使用你的控制器,你可能有这样的代码:

    <div data-ng-controller="YourController as aliasOfYourController">

       Your first pane is {{aliasOfYourController.panes[0]}}

    </div>

完成课程后,我立即对使用 $scope 的代码感到困惑,所以感谢您提及它。
该课程根本没有提到 $scope,他们只是使用 asthis 那么它如何帮助解释差异?
我第一次接触 Angular 是在上面提到的课程中,因为从来没有提到过 $scope,所以我学会了在控制器中只使用 this。问题是,当您开始必须在控制器中处理承诺时,您会遇到很多对 this 的引用问题,并且必须开始执行诸如 var me = this 之类的操作,以从承诺返回函数中引用 this 中的模型.所以正因为如此,我仍然很困惑应该使用哪种方法,$scope 还是 this
@BrunoFinger 不幸的是,每当您执行 Promise 或其他需要大量关闭的东西时,您都需要 var me = this.bind(this) 。它与 Angular 无关。
重要的是要知道 ng-controller="MyCtrl as MC" 相当于将 $scope.MC = this 放入控制器本身 - 它在范围上定义了 MyCtrl 的实例 (this) 以通过 {{ MC.foo }} 在模板中使用
K
Kamil Szot

以前的 Angular 版本(1.0 RC 之前)允许您将其与 $scope 方法互换使用,但现在不再如此。在范围内定义的方法 this 和 $scope 是可互换的(角度将 this 设置为 $scope),但在控制器构造函数内则不能。

要恢复这种行为(有人知道为什么会改变吗?),您可以添加:

return angular.extend($scope, this);

在你的控制器函数结束时(假设 $scope 被注入到这个控制器函数中)。

这具有通过控制器对象访问父范围的良好效果,您可以使用 require: '^myParentDirective' 进入子范围


This article 很好地解释了为什么 this 和 $scope 不同。
A
Aniket Jha

$scope 有一个不同的 'this' 然后控制器 'this'。因此,如果你在控制器中放置一个 console.log(this) 它会给你一个 object(controller) 并且 this.addPane() 将 addPane 方法添加到控制器对象。但是 $scope 的作用域不同,其作用域内的所有方法都需要由 $scope.methodName() 访问。 this.methodName() inside controller 表示在控制器对象中添加方法。$scope.functionName() 是 HTML 和 inside

$scope.functionName(){
    this.name="Name";
    //or
    $scope.myname="myname"//are same}

将此代码粘贴到您的编辑器中并打开控制台以查看...

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
    <script>
        var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
          console.log("ctrl 'this'",this);
          //this(object) of controller different then $scope
          $scope.firstName="Andy";
          $scope.lastName="Bot";
          this.nickName="ABot";
          this.controllerMethod=function(){

            console.log("controllerMethod ",this);
          }
          $scope.show=function(){
              console.log("$scope 'this",this);
              //this of $scope
              $scope.message="Welcome User";
          }

        });
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
       Comming From $SCOPE :{{firstName}}
       <br><br>
       Comming from $SCOPE:{{lastName}}
       <br><br>
       Should Come From Controller:{{nickName}}
       <p>
            Blank nickName is because nickName is attached to 
           'this' of controller.
       </p>

       <br><br>
       <button ng-click="controllerMethod()">Controller Method</button>

       <br><br>
       <button ng-click="show()">Show</button>
       <p>{{message}}</p>

   </div>

</body>
</html>