'this' против $ scope в контроллерах AngularJS

В разделе «Создание компонентов» домашней страницы 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, взаимозаменяемы (angular устанавливает this в $scope), но не внутри конструктора вашего контроллера.

Как this и $scope работают в контроллерах AngularJS?


person Alexei Boronine    schedule 23.07.2012    source источник
comment
Меня это тоже сбивает с толку. Когда представление указывает контроллер (например, ng-controller = '...'), $ scope, связанная с этим контроллером, кажется, идет вместе с ним, потому что представление может получить доступ к свойствам $ scope. Но когда директива требует другого контроллера (а затем использует его в своей функции связывания), область $, связанная с этим другим контроллером, не идет вместе с ним?   -  person Mark Rajcok    schedule 13.09.2012
comment
Эта сбивающая с толку цитата о предыдущих версиях ... уже удалена? Тогда, может быть, обновление будет на месте?   -  person Dmitri Zaitsev    schedule 07.12.2015
comment
Для модульного тестирования, если вы используете 'this' вместо '$ scope', вы не можете внедрить контроллер с имитацией области видимости, и поэтому вы не можете выполнять модульное тестирование. Я не думаю, что использовать «это» - хорошая практика.   -  person abentan    schedule 01.02.2018


Ответы (7)


"Как this и $scope работают в контроллерах AngularJS?"

Краткий ответ:

  • this
    • When the controller constructor function is called, this is the controller.
    • Когда вызывается функция, определенная для объекта $scope, this является «областью действия, когда функция была вызвана». Это может быть (а может и не быть!) $scope, на котором определена функция. Таким образом, внутри функции this и $scope могут не совпадать.
  • $scope
    • Every controller has an associated $scope object.
    • Функция контроллера (конструктора) отвечает за установку свойств модели и функций / поведения связанных с ней $scope.
    • Только методы, определенные в этом объекте $scope (и объекты родительской области, если используется прототипное наследование), доступны из HTML / представления. Например, из ng-click, фильтры и т. Д.

Длинный ответ:

Функция контроллера - это функция конструктора JavaScript. Когда функция конструктора выполняется (например, при загрузке представления), this (т.е. «контекст функции») устанавливается в объект контроллера. Итак, в функции конструктора контроллера "вкладки" при создании функции addPane

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

он создается в объекте контроллера, а не в $ scope. Представления не могут видеть функцию addPane - они имеют доступ только к функциям, определенным в $ scope. Другими словами, в HTML это не сработает:

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

После выполнения функции конструктора контроллера tabs мы имеем следующее:

после функции конструктора контроллера вкладок

Пунктирная черная линия указывает на прототипное наследование - изолированная область видимости прототипически наследуется от Scope. (Он не наследуется прототипом от той области действия, в которой директива была встречена в HTML.)

Теперь функция ссылки директивы панели хочет взаимодействовать с директивой tabs (что на самом деле означает, что она должна каким-то образом влиять на вкладки, изолирующие $ scope). Можно использовать события, но другой механизм - это директива pane require для контроллера вкладок. (Похоже, что для директивы pane require области вкладок $ не существует механизма.)

Итак, возникает вопрос: если у нас есть доступ только к контроллеру вкладок, как мы можем получить доступ к вкладкам, изолирующим $ scope (что мы действительно хотим)?

Что ж, красная пунктирная линия - это ответ. «Область действия» функции addPane () (здесь я имею в виду область / закрытие функции JavaScript) дает функции доступ к вкладкам, изолирующим $ scope. То есть addPane () имеет доступ к «вкладкам IsolateScope» на диаграмме выше из-за закрытия, которое было создано при определении addPane (). (Если бы вместо этого мы определили addPane () в объекте tabs $ scope, директива pane не имела бы доступа к этой функции, и, следовательно, у нее не было бы возможности взаимодействовать с tabs $ 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's $scope. Внутри метода $scope по-прежнему является $ scope ParentCtrl.

Fiddle

Я стараюсь не использовать this внутри функции, определенной в $ scope, так как становится непонятно, какая $ scope затронута, особенно учитывая, что ng-repeat, ng-include, ng-switch и директивы могут создавать свои собственные дочерние области .

person Mark Rajcok    schedule 05.01.2013
comment
Когда вызывается функция, определенная в объекте $ scope, это область действия, действующая на момент вызова функции. Это может быть (а может и не быть!) $ Scope, в которой определена функция. Итак, внутри функции this и $ scope могут не совпадать. Но в официальной документации AngularJS указано, Внутри методов, определенных в области видимости this и $ scope, взаимозаменяемы (angular устанавливает для этого значение $ scope ), но не внутри конструктора вашего контроллера. - person tamakisquare; 19.02.2013
comment
@tamakisquare, я считаю, что цитированный вами полужирный текст применяется к тому, когда вызывается функция конструктора контроллера, то есть когда контроллер создан = связан с $ scope. Это не применяется позже, когда произвольный код JavaScript вызывает метод, определенный для объекта $ scope. - person Mark Rajcok; 20.02.2013
comment
Спасибо, Марк. Я перечитал часть официального документа еще несколько раз; Шестой пункт после примера с пряным контроллером. Я все еще думаю, что в документе говорится, что this и $scope взаимозаменяемы при использовании внутри методов, определенных в области видимости. Здесь не упоминается, что взаимозаменяемость зависит от того, где вызываются методы, поэтому я предполагаю, что это не имеет значения. Но, очевидно, ваш пример доказал обратное. - person tamakisquare; 20.02.2013
comment
Я хотел бы предложить вам открыть билет с помощью github AngularJS, в котором упоминается двусмысленность / неточность официального документа вместе с вашим примером в скрипке. - person tamakisquare; 20.02.2013
comment
@tamakisquare, я должен перечитать свой ответ! Когда вызывается функция конструктора контроллера, this является контроллером, а не $ scope. Итак, я полагаю, что цитата из официальных документов применима только в том случае, если вы вызываете метод $ scope из того места, где действует та же $ scope. - person Mark Rajcok; 20.02.2013
comment
Обратите внимание, что теперь можно вызвать функцию addPane () непосредственно в шаблоне, назвав контроллер: MyController как myctrl, а затем myctrl.addPane (). См. docs.angularjs.org/guide/concepts#controller. - person Christophe Augier; 30.11.2013
comment
Я не понимаю, так почему здесь (jsbin.com/EduxOkuX/2/edit) обе строки одинаковые? - person Royi Namir; 24.01.2014
comment
@RoyiNamir, Из HTML / представления доступны только методы, определенные в объекте $ scope (и объектах родительской области, если используется прототипное наследование) - это включает использование фигурных скобок в представлении, например, {{someScopeVariable}}. Представление не может видеть переменные, которые вы присвоили this в ChildController. - person Mark Rajcok; 24.01.2014
comment
Он не наследуется прототипом от той области действия, в которой директива была встречена в HTML. Похоже, это противоречит следующему исчерпывающему описанию объема ... github.com/angular /angular.js/wiki/Understanding-Scopes - в AngularJS дочерняя область обычно прототипически наследуется от своей родительской области. - person Ian Warburton; 03.05.2014
comment
значит ли, что вам следует избегать this и просто использовать область видимости? или я что-то упускаю? - person ErichBSchulz; 17.05.2014
comment
Слишком много внутренней сложности. - person Inanc Gumus; 07.06.2014
comment
Можно ли вызвать функцию, определенную на контроллере, из консоли браузера? - person martinoss; 01.08.2014
comment
Я хочу добавить ссылку на эту скрипку - ›jsfiddle.net/sbZw7/241 - Здесь Функция logThisAndScope добавляется к дочернему контроллеру. Теперь вы увидите, что он возвращает один и тот же объект для this и $ scope. Это ожидается, потому что ng-click ищет функцию в своей локальной $ scope, а затем перемещается вверх по цепочке областей видимости. - person Nicholas Dynan; 10.10.2014
comment
Это очень информативный ответ, но когда я вернулся с практической проблемой (как вызвать $ scope. $ apply () в методе контроллера, определенном с помощью this) Я не смог это решить. Так что, хотя это все еще полезный ответ, я нахожу сложность, присущую ему, сбивающей с толку. - person dumbledad; 15.12.2014
comment
Внутри метода $ scope по-прежнему является $ scope ParentCtrl ... не могли бы вы объяснить это, пожалуйста? Почему $ scope не является дочерним контроллером $ scope? - person smackenzie; 13.01.2015
comment
@smackenzie, когда функция logThisAndScope () создается, $scope ParentCtrl становится частью закрытия, связанного с функцией. Поэтому, когда функция обращается к переменной $scope, она находит ее в замыкании. - person Mark Rajcok; 13.01.2015
comment
Javascript - много веревки [чтобы повеситься]. - person AlikElzin-kilaka; 01.06.2015
comment
Спасибо за разъяснение, еще одно, почему при нажатии второй ссылки будет показано, что $ scope по-прежнему является $ scope ParentCtrl.? - person user2499325; 28.08.2015
comment
@ user2499325, функция logThisAndScope создает замыкание, а переменная $scope (связанная с ParentCtrl) является частью этого замыкания. Когда щелкают по второй ссылке, logThisAndScope функция не определена для $scope ChildCtrl, поэтому срабатывает прототипное наследование и проверяется родительский $scope (связанный с ParentCtrl). На этом $scope найдена logThisAndScope функция, и она выполняется. Когда эта функция регистрирует $scope, это $scope, связанный с закрытием, следовательно, связанный с ParentCtrl. Надеюсь, это поможет. - person Mark Rajcok; 01.09.2015
comment
Как this работает, когда функция контроллера вызывается через службу $ controller? В частности, ctrlInstance имел бы какие-либо свойства, которые были определены в функции контроллера с помощью this.property = value? ($ scope передается через местных жителей) Думаю, нет. - person Tahsis Claus; 28.10.2015
comment
Я также нашел этот toddmotto.com/digging-into-angulars-controller-as -syntax полезно - person vinesh; 13.01.2016
comment
@ AlikElzin-kilaka, да, это скорее проблема с Angular, а не с JavaScript. - person Dan; 15.09.2016
comment
не используйте это, избегайте этого, если вы не делаете абсолютно чистое ООП. это конструкция ООП, лучше использовать JS в более функциональном стиле из-за вложенных функций. это все. Я не буду использовать это вместо $ scope ни в одном из моих угловых кодов, большое вам спасибо. - person Alexander Mills; 17.03.2017
comment
Хорошее объяснение. Спасибо - person John; 22.01.2018
comment
@MarkRajcok Напрашивается, вопрос не означает то, что вы думаете. Как и прицел, почти каждый неправильно понимает его и пытается использовать неправильно. При правильном использовании эта фраза указывает на логическую ошибку использования посылок в аргументе, которые зависят от предполагаемого вывода, формы кругового рассуждения. - person Peter Wone; 18.05.2020

Причина, по которой ему назначено 'addPane', связана с директивой <pane>.

Директива pane выполняет require: '^tabs', который помещает объект контроллера вкладок из родительской директивы в функцию ссылки.

addPane назначается this, так что функция ссылки pane может его видеть. Затем в функции ссылки pane addPane - это просто свойство контроллера tabs, и это просто tabsControllerObject.addPane. Таким образом, функция связывания директивы панели может получить доступ к объекту контроллера вкладок и, следовательно, получить доступ к методу addPane.

Надеюсь, мое объяснение достаточно ясное ... это довольно сложно объяснить.

person Andrew Joslin    schedule 23.07.2012
comment
Спасибо за объяснение. Документы создают впечатление, что контроллер - это просто функция, которая устанавливает область видимости. Почему контроллер воспринимается как объект, если все действия происходят в области видимости? Почему бы просто не передать родительскую область видимости в функцию связывания? Изменить: чтобы лучше сформулировать этот вопрос, если методы контроллера и методы области работают с одной и той же структурой данных (область действия), почему бы не поместить их все в одном месте? - person Alexei Boronine; 24.07.2012
comment
Похоже, что родительская область не передается в функцию lnk из-за желания поддерживать повторно используемые компоненты, которые не должны случайно читать или изменять данные в родительской области. Но если директива действительно хочет / нуждается в чтении или изменении НЕКОТОРЫХ КОНКРЕТНЫХ данных в родительской области (как это делает директива 'pane'), она требует некоторых усилий: 'требовать' контроллер, в котором находится желаемая родительская область, затем определить на этом контроллере (используйте this, а не $ scope) для доступа к конкретным данным. Поскольку желаемая родительская область видимости не вводится в функцию lnk, я полагаю, что это единственный способ сделать это. - person Mark Rajcok; 13.09.2012
comment
Привет, Марк, на самом деле легче изменить область действия директивы. Вы можете просто использовать функцию ссылки jsfiddle.net/TuNyj - person Andrew Joslin; 13.09.2012
comment
Спасибо @Andy за скрипку. В вашей скрипке директива не создает новую область видимости, поэтому я вижу, как функция ссылки может напрямую обращаться к области действия контроллера (поскольку существует только одна область). Директивы вкладок и панели используют изолирующие области (т. Е. Создаются новые дочерние области, которые не наследуются прототипом от родительской области). Для случая изолированной области кажется, что определение метода на контроллере (с использованием this) - единственный способ разрешить другой директиве получить (косвенный) доступ к другой (изолированной) области. - person Mark Rajcok; 01.01.2013

Я только что прочитал довольно интересное объяснение разницы между ними и растущее предпочтение прикреплять модели к контроллеру и использовать псевдоним контроллера для привязки моделей к представлению. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ - это статья.

ПРИМЕЧАНИЕ. Исходная ссылка все еще существует, но из-за изменений форматирования ее трудно читать. Легче просмотреть в оригинале.

Он не упоминает об этом, но при определении директив, если вам нужно поделиться чем-то между несколькими директивами и вам не нужна услуга (есть законные случаи, когда услуги являются проблемой), тогда прикрепите данные к контроллеру родительской директивы.

Служба $scope предоставляет множество полезных вещей, из которых $watch является наиболее очевидным, но если все, что вам нужно для привязки данных к представлению, использование простого контроллера и 'controller as' в шаблоне нормально и, возможно, предпочтительнее.

person Derek    schedule 28.08.2014

Я рекомендую вам прочитать следующий пост: AngularJS: "Controller as" или "$ scope" ?

Он очень хорошо описывает преимущества использования «Контроллер как» для раскрытия переменных по сравнению с «$ scope».

Я знаю, что вы спрашивали конкретно о методах, а не о переменных, но я думаю, что лучше придерживаться одной техники и согласовываться с ней.

Так что, на мой взгляд, из-за проблемы переменных, обсуждаемой в посте, лучше просто использовать технику «Контроллер как», а также применить ее к методам.

person Liran Brimer    schedule 03.11.2015

В этом курсе (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>
person Sandro    schedule 24.09.2014
comment
Пройдя курс, я сразу был сбит с толку кодом, использующим $scope, поэтому спасибо, что упомянули об этом. - person Matt Montag; 11.12.2014
comment
В этом курсе вообще не упоминается $ scope, они просто используют as и this, так как это может помочь объяснить разницу? - person dumbledad; 11.12.2014
comment
Мое первое знакомство с Angular было из упомянутого курса, и, поскольку $scope никогда не упоминался, я научился использовать только this в контроллерах. Проблема в том, что когда вы начинаете обрабатывать обещания в своем контроллере, у вас возникает много проблем со ссылками на this, и вам нужно начинать делать такие вещи, как var me = this, чтобы ссылаться на модель в this из функции возврата обещания. Поэтому я до сих пор не понимаю, какой метод мне следует использовать - $scope или this. - person Bruno Finger; 11.03.2015
comment
@BrunoFinger К сожалению, вам понадобятся var me = this или .bind(this) всякий раз, когда вы выполняете обещания или другие вещи, связанные с закрытием. Это не имеет ничего общего с Angular. - person Dzmitry Lazerka; 18.03.2016
comment
@DzmitryLazerka Вы абсолютно правы, но если мы будем достаточно осторожны, мы сможем избежать написания такого уродливого кода. На самом деле, перечитывая сегодня мой комментарий, я даже стесняюсь сказать что-то вроде обработки обещаний внутри контроллера. Такая логика нужна где-то еще в приложении. Я стремлюсь к тому, чтобы они были на другом уровне обслуживания, взаимодействующем с $scope. Это делает сложную логику более пригодной для повторного использования. - person Bruno Finger; 21.03.2016
comment
Важно знать, что ng-controller="MyCtrl as MC" эквивалентно помещению $scope.MC = this в сам контроллер - он определяет экземпляр (this) MyCtrl в области видимости для использования в шаблоне через {{ MC.foo }} - person William B; 25.11.2016

Предыдущие версии Angular (до 1.0 RC) позволяли использовать это взаимозаменяемо с методом $ scope, но это уже не так. Внутри методов, определенных в области видимости, this и $ scope взаимозаменяемы (angular устанавливает this в $ scope), но не внутри конструктора вашего контроллера.

Чтобы вернуть это поведение (кто-нибудь знает, почему оно было изменено?), Вы можете добавить:

return angular.extend($scope, this);

в конце вашей функции контроллера (при условии, что $ scope была введена в эту функцию контроллера).

Это дает хороший эффект доступа к родительской области через объект контроллера, который вы можете получить в дочернем элементе с помощью require: '^myParentDirective'

person Kamil Szot    schedule 20.06.2014
comment
Эта статья дает хорошее объяснение того, почему this и $ scope другой. - person Robert Martin; 08.01.2015

$ scope имеет другое 'this', чем контроллер 'this'. Таким образом, если вы поместите console.log (this) внутри контроллера, он даст вам объект (контроллер), а 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>
person Aniket Jha    schedule 07.02.2018