AngularJS控制器中的'this'与$ scope


1026

AngularJS主页“创建组件”部分中,有以下示例:

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之前的版本)允许您this与该$scope方法互换使用,但情况不再如此。在作用域上定义的方法内部this并且$scope可以互换(将设置this$scope),但在控制器构造函数中则不能如此。

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


我也感到困惑。当视图指定控制器时(例如ng-controller ='...'),与该控制器关联的$ scope似乎也随之出现,因为该视图可以访问$ scope属性。但是,当指令“需要”另一个控制器(然后在其链接功能中使用它)时,与该另一个控制器关联的$ scope是否不附带?
Mark Rajcok 2012年

现在是否已删除有关“以前的版本...”的令人困惑的报价?然后,也许更新就位了?
德米特里·扎伊采夫

对于单元测试,如果使用“ this”而不是“ $ scope”,则不能为控制器注入模拟范围,因此不能进行单元测试。我认为使用“ this”不是一个好习惯。
abentan '18年

Answers:


999

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

简短答案

  • this
    • 调用控制器构造函数时,this就是控制器。
    • 调用在$scope对象上定义的函数时,this是“调用该函数时有效的作用域”。这可能是(也可能不是!)$scope函数定义的依据。所以,在函数内部,this并且$scope可能相同。
  • $scope
    • 每个控制器都有一个关联的$scope对象。
    • 控制器(构造函数)功能负责在其关联的上设置模型属性和功能/行为$scope
    • $scope可以从HTML /视图访问在此对象(和父作用域对象,如果正在进行原型继承的情况)上定义的方法。例如,来自ng-click,过滤器等。

长答案

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

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

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

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

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

Tabs控制器构造函数之后

黑色虚线表示原型继承-一个隔离范围原型从Scope继承。(它并不是从HTML中遇到该指令的有效范围继承的原型。)

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

因此,这就引出了一个问题:如果我们仅有权使用tabs控制器,那么我们如何获得对选项卡的隔离$ scope(这是我们真正想要的)?

好吧,红色虚线就是答案。addPane()函数的“作用域”(我在这里指的是JavaScript的函数作用域/闭包)使函数可以访问制表符隔离$ scope。即,由于在定义addPane()时创建了一个闭包,因此addPane()可以访问上图中的“选项卡IsolateScope”。(如果改为在选项卡的$ scope对象上定义addPane(),则panel指令将无法访问此函数,因此它将无法与选项卡的$ 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(Solely)有

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

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

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

小提琴

我试着不要this在$ scope上定义的函数中使用它,因为这会影响哪个$ scope,尤其是考虑到ng-repeat,ng-include,ng-switch和指令都可以创建自己的子作用域,这使我感到困惑。


6
@tamakisquare,我相信您引用的粗体文本适用于调用控制器构造函数的时候,即,当控制器创建时=与$ scope关联。以后,当任意JavaScript代码调用在$ scope对象上定义的方法时,该方法将不再适用。
Mark Rajcok

79
请注意,现在可以通过以下方式直接在模板中调用addPane()函数:将控制器命名为“ MyController as myctrl”,然后命名为myctrl.addPane()。参见docs.angularjs.org/guide/concepts#controller
Christophe Augier

81
太多固有的复杂性。
Inanc Gumus 2014年

11
这是一个非常有用的答案,但是当我遇到一个实际问题(如何在使用'this'定义的控制器方法中调用$ scope。$ apply())时,我无法解决。因此,尽管这仍然是一个有用的答案,但我发现“固有的复杂性”令人困惑。
dumbledad 2014年

11
Javascript-很多绳子[吊死自己]。
AlikElzin-kilaka 2015年

55

之所以将“ addPane”分配给它是因为该<pane>指令。

pane指令确实require: '^tabs',这使突片控制器对象从父指令,到链接功能。

addPane被分配到this,这样的pane链接功能可以看到它。然后在pane链接函数中,addPane它只是tabs控制器的属性,而只是tabsControllerObject.addPane。因此,窗格指令的链接功能可以访问tabs控制器对象,因此可以访问addPane方法。

我希望我的解释很清楚..这很难解释。


3
感谢您的解释。文档似乎使控制器只是设置作用域的功能。如果所有动作都在示波器中发生,为什么控制器会被当作对象对待?为什么不将父范围传递给链接函数呢?编辑:为了更好地表达这个问题,如果控制器方法和作用域方法都在同一个数据结构(作用域)上运行,为什么不将它们全部放在一个位置?
Alexei Boronine 2012年

似乎由于希望支持“可重用组件,不应意外读取或修改父范围中的数据”,所以父范围未传递到lnk函数中。但是,如果某个指令确实确实希望/需要读取或修改父作用域中的某些特定数据(如“ pane”指令一样),则需要付出一些努力:“要求”控制器所需父作用域的位置,然后定义一个该控制器上的方法(使用'this'而不是$ scope)来访问特定数据。由于所需的父作用域没有注入到lnk函数中,因此我想这是唯一的方法。
Mark Rajcok 2012年

1
嘿,标记,实际上修改指令的范围更容易。您可以只使用链接函数jsfiddle.net/TuNyj
Andrew Joslin

3
感谢@Andy的小提琴。在您的摆弄中,该指令没有创建新的作用域,因此我可以在这里看到链接函数如何直接访问控制器的作用域(因为只有一个作用域)。选项卡和窗格指令使用隔离范围(即,创建的新子范围并不典型地从父范围继承)。对于隔离范围的情况,似乎在控制器上定义方法(使用“ this”)是允许另一个指令(间接)访问另一个(隔离)范围的唯一方法。
Mark Rajcok

27

我刚刚读到了关于两者之间区别的一个非常有趣的解释,并且越来越喜欢将模型附加到控制器,并为控制器添加别名以将模型绑定到视图。本文是http://toddmotto.com/digging-into-angulars-controller-as-syntax/
他没有提到它,但是在定义指令时,如果您需要在多个指令之间共享某些内容并且不想要服务(在某些情况下,服务很麻烦),则将数据附加到父指令的控制器。

$scope服务提供了很多有用的功能,$watch这是最显而易见的,但是如果您需要将数据绑定到视图,则可以在模板中使用普通控制器和“ controller as”很好,并且可以说是更可取的。


20

我建议您阅读以下文章: AngularJS:“ Controller as”还是“ $ scope”?

它很好地描述了使用“ Controller as”来公开变量而不是“ $ scope”的优点。

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

因此,根据我的观点,由于帖子中讨论了变量问题,因此最好只使用“ Controller as”技术并将其应用于方法。


16

在本课程中(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>

6
学习完本课程后,我立刻被使用的代码所迷惑$scope,因此感谢您提及它。
马特·蒙塔格

16
该课程根本没有提到$ scope,它们只是使用而已asthis那么它如何帮助解释差异?
dumbledad 2014年

10
我与Angular的第一次接触来自上述课程,而且$scope从未有过提及,我学会了仅this在控制器中使用。问题是,当您开始在控制器中处理promise时,您会遇到很多引用问题,this并且必须开始做一些事情,例如从promise return函数中var me = this引用模型this。所以,因为这,我还是很困惑我应该使用哪种方法,$scopethis
Bruno Finger

@BrunoFinger不幸的是,您将需要var me = this.bind(this)每当需要做Promises或其他需要大量关闭的工作时。它与Angular无关。
Dzmitry Lazerka '16

1
重要的是要知道这ng-controller="MyCtrl as MC"等同于放入$scope.MC = this控制器本身–它通过{{ MC.foo }}
William B

3

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

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

return angular.extend($scope, this);

在控制器功能的末尾(假设$ scope已注入此控制器功能)。

这具有很好的效果,即可以通过控制器对象访问父级作用域,而您可以使用 require: '^myParentDirective'


7
本文很好地解释了为什么它和$ scope不同。
罗伯特·马丁

1

$ scope具有与控制器``this''不同的``this''。因此,如果将console.log(this)放入控制器内部,它将为您提供一个object(controller),this.addPane()将addPane方法添加到控制器Object。但是$ scope具有不同的范围,并且其范围内的所有方法都必须由$ scope.methodName()访问。 this.methodName()内部控制器意味着在控制器对象内部添加方法。$scope.functionName()在HTML和内部

$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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.