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


在里面 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控制器中工作?


965
2017-07-23 02:55


起源


我发现这也令人困惑。当视图指定控制器(例如,ng-controller ='...')时,与该控制器关联的$ scope似乎随之出现,因为该视图可以访问$ scope属性。但是当一个指令'require'的另一个控制器(然后在它的链接函数中使用它)时,与该另一个控制器相关联的$ scope不会随之出现? - Mark Rajcok
关于“以前的版本......”这个令人困惑的引用现在被删除了吗?那么也许更新会到位? - Dmitri Zaitsev
对于单元测试,如果使用'this'而不是'$ scope',则不能向控制器注入模拟范围,因此您无法进行单元测试。我不认为使用'this'是一个好习惯。 - abentan


答案:


“如何 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”控制器构造函数后,我们有以下内容:

after tabs controller constructor function

黑色虚线表示原型继承 - 原型继承的孤立范围 范围。 (它没有原型地继承HTML中遇到指令的有效范围。)

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

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

嗯,红色虚线就是答案。 addPane()函数的“范围”(我指的是JavaScript的函数范围/闭包)给函数访问选项卡隔离$ scope。即,addPane()可以访问上图中的“tabs 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的范围。

小提琴

我尽量不使用 this 在$ scope中定义的函数内部,因为会影响$ scope受到影响,特别是考虑到ng-repeat,ng-include,ng-switch和指令都可以创建自己的子作用域。


950
2018-01-05 04:48



@tamakisquare,我相信你引用的粗体文本适用于调用控制器构造函数时 - 即,当创建控制器=与$ scope相关联时。当任意JavaScript代码调用$ scope对象上定义的方法时,它不适用于以后。 - Mark Rajcok
注意,现在可以通过命名控制器直接在模板中调用addPane()函数:“MyController as myctrl”,然后是myctrl.addPane()。看到 docs.angularjs.org/guide/concepts#controller - Christophe Augier
固有的复杂性太多了。 - Inanc Gumus
这是一个非常丰富的答案,但当我回来时遇到一个实际问题(如何在使用'this'定义的控制器方法中调用$ scope。$ apply()我无法解决这个问题。因此,虽然这仍然是一个有用的答案,但我发现“固有的复杂性”令人费解。 - dumbledad
Javascript - 很多绳子[挂起自己]。 - AlikElzin-kilaka


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

pane 指令呢 require: '^tabs',将来自父指令的tabs控制器对象放入链接函数中。

addPane 分配给 this 所以这样 pane 链接功能可以看到它。然后在 pane 链接功能, addPane 只是一个属性 tabs 控制器,它只是tabsControllerObject.addPane。因此,pane指令的链接功能可以访问tabs控制器对象,从而访问addPane方法。

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


50
2017-07-23 15:20



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


我刚刚阅读了关于两者之间差异的一个非常有趣的解释,并且越来越倾向于将模型附加到控制器并且将控制器别名为控制器以将模型绑定到视图。 http://toddmotto.com/digging-into-angulars-controller-as-syntax/ 是文章。他没有提到它,但是在定义指令时,如果你需要在多个指令之间共享某些内容并且不需要服务(有合理的情况,服务很麻烦),那么将数据附加到父指令的控制器。 $ scope服务提供了大量有用的东西,$ watch是最明显的,但是如果你只需要将数据绑定到视图,那么在模板中使用普通控制器和'controller as'是可以的,并且可以说是更好的选择。


26
2017-08-28 21:25





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

如果通过“this”方法向控制器添加方法,则必须在视图中使用控制器名称“dot”将其称为属性或方法。

例如,在视图中使用您的控制器,您可能具有以下代码:

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

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

    </div>

15
2017-09-24 07:29



完成课程后,我立即对使用的代码感到困惑 $scope,谢谢你提到它。 - Matt Montag
那个课程根本没有提到$ scope,他们只是使用 as 和 this 那怎么能帮助解释这种差异呢? - dumbledad
我与Angular的第一次接触来自于上述课程,而且是 $scope 从未被提及,我学会了使用 this 在控制器中。问题是当你开始在你的控制器中处理承诺时,你会遇到很多引用问题 this 并且必须开始做类似的事情 var me = this 参考模型 this 来自承诺返回功能。因此,我仍然对我应该使用哪种方法感到困惑, $scope 要么 this。 - Bruno Finger
@BrunoFinger不幸的是,你需要 var me = this 要么 .bind(this) 每当你做Promise,或其他关闭重的东西。它与Angular无关。 - Dzmitry Lazerka
@DzmitryLazerka你是完全正确但如果我们足够小心,我们可以避免编写这样丑陋的代码。事实上,今天重新阅读我的评论,我甚至不好意思在控制器中说出一些处理承诺。这种逻辑属于应用程序中的其他位置。我正在努力让他们进入另一层服务接口 $scope。这使复杂的逻辑更加可重用。 - Bruno Finger


我建议你阅读以下帖子: http://codetunnel.io/angularjs-controller-as-or-scope/

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

我知道你具体问过方法而不是变量,但我认为最好坚持使用一种技术并与之保持一致。

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


15
2017-11-03 10:31





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

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

return angular.extend($scope, this);

在控制器功能结束时(前提是$ scope注入此控制器功能)。

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


3
2018-06-20 08:46



本文 提供了一个很好的解释为什么这和$范围不同。 - Robert Martin


$ scope有一个不同的'this'然后是控制器'this'。因此,如果你把一个console.log(这个)放在控制器中它给你一个对象(控制器),this.addPane()将addPane方法添加到控制器对象。但$ 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>

0
2018-02-07 09:06