'this' vs $ scope dalam pengontrol AngularJS

1027

Di bagian "Buat Komponen" di beranda AngularJS , ada contoh ini:

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);
  }
}

Perhatikan bagaimana selectmetode ditambahkan $scope, tetapi addPanemetode ditambahkan this. Jika saya mengubahnya $scope.addPane, kodenya rusak.

Dokumentasi mengatakan bahwa sebenarnya ada perbedaan, tetapi tidak menyebutkan apa perbedaannya:

Versi Angular sebelumnya (pra 1.0 RC) memungkinkan Anda untuk menggunakan metode ini secara thisbergantian $scope, tetapi ini tidak lagi menjadi masalah. Di dalam metode yang didefinisikan pada ruang lingkup thisdan $scopedapat dipertukarkan (set sudut thiske $scope), tetapi tidak sebaliknya di dalam konstruktor controller Anda.

Bagaimana thisdan $scopebekerja di pengontrol AngularJS?

Alexei Boronine
sumber
Saya menemukan ini membingungkan juga. Ketika sebuah view menentukan sebuah controller (mis., Ng-controller = '...'), $ scope yang terkait dengan controller itu sepertinya ikut serta, karena view dapat mengakses $ lingkup properti. Tetapi ketika sebuah direktif 'memerlukan pengontrol lain (dan kemudian menggunakannya dalam fungsi penghubungnya), $ scope yang terkait dengan pengontrol lain tidak datang bersamanya?
Mark Rajcok
Apakah itu kutipan membingungkan tentang "Versi sebelumnya ..." telah dihapus sekarang? Maka mungkin pembaruan akan ada di tempat?
Dmitri Zaitsev
Untuk pengujian unit, jika Anda menggunakan 'ini' alih-alih '$ lingkup', Anda tidak dapat menyuntikkan controller dengan cakupan yang diolok-olok, sehingga Anda tidak dapat melakukan pengujian unit. Saya pikir ini bukan praktik yang baik untuk menggunakan 'ini'.
abentan

Jawaban:

999

"Bagaimana thisdan $scopebekerja di pengontrol AngularJS?"

Jawaban singkat :

  • this
    • Ketika fungsi konstruktor controller dipanggil, thisadalah controller.
    • Ketika suatu fungsi yang didefinisikan pada suatu $scopeobjek dipanggil, thisadalah "ruang lingkup efek ketika fungsi itu dipanggil". Ini mungkin (atau mungkin tidak!) $scopeAdalah fungsi yang didefinisikan. Jadi, di dalam fungsinya, thisdan $scopemungkin tidak sama.
  • $scope
    • Setiap pengontrol memiliki $scopeobjek terkait .
    • Fungsi pengontrol (konstruktor) bertanggung jawab untuk mengatur properti model dan fungsi / perilaku yang terkait $scope.
    • Hanya metode yang didefinisikan pada $scopeobjek ini (dan objek lingkup induk, jika warisan prototipikal sedang dimainkan) dapat diakses dari HTML / view. Misalnya, dari ng-click, filter, dll.

Jawaban panjang :

Fungsi pengontrol adalah fungsi konstruktor JavaScript. Ketika fungsi konstruktor dijalankan (misalnya, ketika tampilan dimuat), this(yaitu, "konteks fungsi") diatur ke objek pengontrol. Jadi dalam fungsi konstruktor pengontrol "tab", ketika fungsi addPane dibuat

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

itu dibuat pada objek controller, bukan pada $ scope. Tampilan tidak dapat melihat fungsi addPane - mereka hanya memiliki akses ke fungsi yang didefinisikan pada $ scope. Dengan kata lain, dalam HTML, ini tidak akan berfungsi:

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

Setelah fungsi konstruktor pengontrol "tab" dijalankan, kami memiliki yang berikut:

setelah fungsi konstruktor tab tab

Garis hitam putus-putus menunjukkan pewarisan prototipal - ruang lingkup prototip mewarisi dari Lingkup . (Ini tidak mewarisi secara prototipik dari ruang lingkup yang berlaku di mana arahan ditemukan dalam HTML.)

Sekarang, fungsi tautan pane direktif ingin berkomunikasi dengan direktif tab (yang benar-benar berarti perlu mempengaruhi tab mengisolasi $ scope dalam beberapa cara). Acara dapat digunakan, tetapi mekanisme lain adalah memiliki panel direktif requirepengontrol tab. (Tampaknya tidak ada mekanisme untuk arahan panel ke requiretab $ lingkup.)

Jadi, ini menimbulkan pertanyaan: jika kita hanya memiliki akses ke tabs controller, bagaimana kita mendapatkan akses ke tabs isolate $ scope (yang sebenarnya kita inginkan)?

Nah, garis putus-putus merah adalah jawabannya. "Scope" fungsi addPane () (saya mengacu pada lingkup fungsi / penutupan JavaScript di sini) memberikan akses fungsi ke tabs isolate $ scope. Yaitu, addPane () memiliki akses ke "tab IsolateScope" pada diagram di atas karena penutupan yang dibuat ketika addPane () didefinisikan. (Jika sebaliknya kami mendefinisikan addPane () pada objek $ lingkup objek, arahan panel tidak akan memiliki akses ke fungsi ini, dan karenanya itu tidak akan memiliki cara untuk berkomunikasi dengan tab $ lingkup.)

Untuk menjawab bagian lain dari pertanyaan Anda how does $scope work in controllers?::

Dalam fungsi yang didefinisikan pada $ scope, thisdiset ke "$ scope yang berlaku di mana / kapan fungsi dipanggil". Misalkan kita memiliki HTML berikut:

<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>

Dan ParentCtrl(Hanya) memiliki

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

Mengklik tautan pertama akan menunjukkan hal itu thisdan $scopesama, karena " ruang lingkup efek ketika fungsi dipanggil " adalah ruang lingkup yang terkait dengan ParentCtrl.

Mengklik link kedua akan mengungkapkan thisdan $scopeyang tidak sama, karena " lingkup berlaku ketika fungsi dipanggil " adalah ruang lingkup terkait dengan ChildCtrl. Jadi di sini, thisdiatur untuk ChildCtrl's $scope. Di dalam metode, $scopemasih ParentCtrl$ lingkup.

Biola

Saya mencoba untuk tidak menggunakan thisdi dalam fungsi yang didefinisikan pada $ scope, karena menjadi membingungkan $ scope mana yang terpengaruh, terutama mengingat bahwa ng-repeat, ng-include, ng-switch, dan arahan semua dapat membuat cakupan anak mereka sendiri.

Mark Rajcok
sumber
6
@tamakisquare, saya percaya teks tebal yang Anda kutip berlaku ketika fungsi konstruktor controller dipanggil - yaitu, ketika controller dibuat = terkait dengan $ scope. Itu tidak berlaku kemudian, ketika kode JavaScript sewenang-wenang memanggil metode yang didefinisikan pada objek $ scope.
Mark Rajcok
79
Perhatikan bahwa sekarang mungkin untuk memanggil fungsi addPane () langsung di templat dengan memberi nama controller: "MyController as myctrl" dan kemudian myctrl.addPane (). Lihat docs.angularjs.org/guide/concepts#controller
Christophe Augier
81
Terlalu banyak kompleksitas yang melekat.
Inanc Gumus
11
Ini adalah jawaban yang sangat informatif, tetapi ketika saya kembali dengan masalah praktis ( bagaimana memanggil $ scope. $ Apply () dalam metode pengontrol yang didefinisikan menggunakan 'this' ) saya tidak dapat menyelesaikannya. Jadi sementara ini masih merupakan jawaban yang berguna, saya menemukan "kompleksitas yang melekat" membingungkan.
Dumbledad
11
Javascript - banyak tali [untuk menggantung diri].
AlikElzin-kilaka
55

Alasan 'addPane' ditugaskan untuk ini adalah karena <pane>arahan.

The panedirektif tidak require: '^tabs', yang menempatkan objek tab controller dari direktif orang tua, ke dalam fungsi link.

addPaneditugaskan thisagar panefungsi tautan dapat melihatnya. Kemudian pada panefungsi tautan, addPanehanyalah properti tabscontroller, dan itu hanya tabsControllerObject.addPane. Jadi fungsi penghubung panel pane dapat mengakses objek pengontrol tab dan karenanya mengakses metode addPane.

Saya harap penjelasan saya cukup jelas .. agak sulit untuk dijelaskan.

Andrew Joslin
sumber
3
Terima kasih untuk penjelasannya. Dokumen membuatnya tampak bahwa pengontrol hanyalah fungsi yang mengatur ruang lingkup. Mengapa controller diperlakukan seperti objek jika semua tindakan terjadi dalam ruang lingkup? Mengapa tidak hanya meneruskan lingkup induk ke fungsi tautan? Sunting: Untuk ungkapan yang lebih baik dari pertanyaan ini, jika metode pengontrol dan metode lingkup keduanya beroperasi pada struktur data yang sama (ruang lingkup), mengapa tidak meletakkan semuanya di satu tempat?
Alexei Boronine
Tampaknya ruang lingkup induk tidak diteruskan ke fungsi lnk karena keinginan untuk mendukung "komponen yang dapat digunakan kembali, yang seharusnya tidak secara tidak sengaja membaca atau memodifikasi data dalam lingkup induk." Tetapi jika arahan benar-benar ingin / perlu membaca atau memodifikasi BEBERAPA data spesifik dalam lingkup induk (seperti arahan 'pane' lakukan), itu memerlukan beberapa upaya: 'memerlukan' controller di mana lingkup induk yang diinginkan adalah, kemudian tentukan sebuah metode pada pengontrol itu (gunakan 'ini' bukan $ lingkup) untuk mengakses data tertentu. Karena ruang lingkup induk yang diinginkan tidak disuntikkan ke lnk func, saya kira ini adalah satu-satunya cara untuk melakukannya.
Mark Rajcok
1
Hei tandai, sebenarnya lebih mudah untuk memodifikasi ruang lingkup direktif. Anda bisa menggunakan fungsi tautan jsfiddle.net/TuNyj
Andrew Joslin
3
Terima kasih @Andy untuk biola. Di biola Anda, arahan tidak menciptakan lingkup baru, jadi saya bisa melihat bagaimana fungsi tautan dapat langsung mengakses lingkup pengontrol di sini (karena hanya ada satu lingkup). Arahan tab dan panel menggunakan cakupan terisolasi (yaitu, lingkup anak baru dibuat yang tidak mewarisi secara prototipik dari lingkup induk). Untuk kasus lingkup isolasi, tampaknya mendefinisikan metode pada pengontrol (menggunakan 'ini') adalah satu-satunya cara untuk memungkinkan arahan lain untuk mendapatkan (tidak langsung) akses ke ruang lingkup (terisolasi) lainnya.
Mark Rajcok
27

Saya baru saja membaca penjelasan yang cukup menarik tentang perbedaan antara keduanya, dan semakin banyak preferensi untuk melampirkan model ke controller dan alias controller untuk mengikat model ke tampilan. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ adalah artikelnya.
Dia tidak menyebutkannya tetapi ketika mendefinisikan arahan, jika Anda perlu berbagi sesuatu antara banyak arahan dan tidak menginginkan layanan (ada kasus yang sah di mana layanan merepotkan) kemudian lampirkan data ke pengontrol direktif induk.

The $scopelayanan menyediakan banyak hal yang berguna, $watchyang paling jelas, tetapi jika semua yang Anda butuhkan untuk data yang mengikat pandangan, menggunakan kontroler polos dan 'controller sebagai' dalam template baik-baik saja dan bisa dibilang lebih.

Derek
sumber
20

Saya sarankan Anda untuk membaca posting berikut: AngularJS: "Controller as" atau "$ scope"?

Ini menjelaskan dengan sangat baik keuntungan menggunakan "Controller sebagai" untuk mengekspos variabel lebih dari "$ scope".

Saya tahu Anda bertanya secara spesifik tentang metode dan bukan variabel, tapi saya pikir lebih baik tetap berpegang pada satu teknik dan konsisten dengan itu.

Jadi menurut saya, karena masalah variabel yang dibahas dalam posting, lebih baik menggunakan teknik "Controller as" dan juga menerapkannya pada metode.

Liran Brimer
sumber
16

Dalam kursus ini ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) mereka menjelaskan cara menggunakan "ini" dan banyak hal lainnya.

Jika Anda menambahkan metode ke pengontrol melalui metode "ini", Anda harus memanggilnya di tampilan dengan nama pengontrol "titik" properti atau metode Anda.

Misalnya menggunakan controller Anda dalam tampilan Anda mungkin memiliki kode seperti ini:

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

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

    </div>
Sandro
sumber
6
Setelah melalui kursus, saya langsung bingung dengan penggunaan kode $scope, jadi terima kasih sudah menyebutkannya.
Matt Montag
16
Kursus itu tidak menyebutkan $ scope sama sekali, mereka hanya menggunakan asdan thisjadi bagaimana bisa membantu menjelaskan perbedaannya?
dumbledad
10
Sentuhan pertama saya dengan Angular berasal dari kursus yang disebutkan, dan seperti $scopeyang tidak pernah disebutkan, saya belajar menggunakan hanya thisdi controller. Masalahnya adalah ketika Anda mulai memiliki untuk menangani janji-janji di controller Anda, Anda memiliki banyak masalah referensi thisdan harus mulai melakukan hal-hal seperti var me = thisreferensi model thisdari dalam dalam fungsi pengembalian janji. Jadi karena itu, saya masih sangat bingung tentang metode mana yang harus saya gunakan, $scopeatau this.
Bruno Finger
@BrunoFinger Sayangnya, Anda akan membutuhkan var me = thisatau .bind(this)kapan pun Anda melakukan Janji, atau hal-hal berat lainnya. Ini tidak ada hubungannya dengan Angular.
Dzmitry Lazerka
1
Yang penting adalah untuk mengetahui bahwa ng-controller="MyCtrl as MC"itu setara dengan menempatkan $scope.MC = thisdi controller itu sendiri - itu mendefinisikan contoh (ini) dari MyCtrl pada ruang lingkup untuk digunakan dalam template via{{ MC.foo }}
William B
3

Versi Angular sebelumnya (pra 1.0 RC) memungkinkan Anda untuk menggunakan ini secara bergantian dengan metode $ scope, tetapi ini tidak lagi menjadi masalah. Di dalam metode yang didefinisikan pada scope this dan $ scope dapat dipertukarkan (angular menyetel ini ke $ scope), tetapi tidak sebaliknya di dalam konstruktor controller Anda.

Untuk mengembalikan perilaku ini (apakah ada yang tahu mengapa itu diubah?) Anda dapat menambahkan:

return angular.extend($scope, this);

di akhir fungsi pengontrol Anda (asalkan $ scope disuntikkan ke fungsi pengontrol ini).

Ini memiliki efek yang bagus untuk memiliki akses ke ruang lingkup orang tua melalui objek pengontrol yang dapat Anda dapatkan pada anak require: '^myParentDirective'

Kamil Szot
sumber
7
Artikel ini memberikan penjelasan yang baik tentang mengapa ini dan $ lingkup berbeda.
Robert Martin
1

$ scope memiliki 'this' yang berbeda maka controller 'this'. Maka jika Anda meletakkan console.log (this) di dalam controller, ia memberi Anda objek (controller) dan this.addPane () menambahkan Metode addPane ke Object controller. Tetapi $ scope memiliki cakupan yang berbeda dan semua metode dalam cakupannya perlu diakses oleh $ scope.methodName (). this.methodName()inside controller artinya menambahkan methos di dalam objek controller. $scope.functionName()dalam HTML dan di dalam

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

Rekatkan kode ini di editor Anda dan buka konsol untuk melihat ...

 <!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>
Aniket Jha
sumber