Jawaban cepat :
Lingkup anak biasanya secara bawaan mewarisi dari lingkup induknya, tetapi tidak selalu. Satu pengecualian untuk aturan ini adalah direktif dengan scope: { ... }
- ini menciptakan ruang lingkup "mengisolasi" yang tidak mewarisi secara prototipe. Konstruk ini sering digunakan ketika membuat arahan "komponen yang dapat digunakan kembali".
Sedangkan untuk nuansa, pewarisan lingkup biasanya langsung ... sampai Anda membutuhkan pengikatan data 2 arah (yaitu, elemen bentuk, model-ng) di lingkup anak. Ng-repeat, ng-switch, dan ng-include dapat membuat Anda tersandung jika Anda mencoba untuk mengikat ke primitif (misalnya, angka, string, boolean) dalam lingkup induk dari dalam lingkup anak. Ini tidak berfungsi seperti yang diharapkan kebanyakan orang. Ruang lingkup anak mendapatkan properti sendiri yang menyembunyikan / membayangi properti induk dengan nama yang sama. Solusi Anda adalah
- tentukan objek dalam induk untuk model Anda, kemudian referensi properti objek itu pada anak: parentObj.someProp
- gunakan $ parent.parentScopeProperty (tidak selalu memungkinkan, tetapi lebih mudah dari 1. jika memungkinkan)
- tentukan fungsi pada lingkup orang tua, dan panggil dari anak (tidak selalu memungkinkan)
New AngularJS pengembang sering tidak menyadari bahwa ng-repeat
, ng-switch
, ng-view
, ng-include
dan ng-if
semua membuat lingkup anak yang baru, sehingga masalah sering muncul ketika arahan ini terlibat. (Lihat contoh ini untuk ilustrasi singkat masalah.)
Masalah dengan primitif ini dapat dengan mudah dihindari dengan mengikuti "praktik terbaik" untuk selalu memiliki '.' di ng-model Anda - tonton 3 menit. Misko menunjukkan masalah mengikat primitif dengan ng-switch
.
Memiliki sebuah '.' dalam model Anda akan memastikan bahwa warisan prototypal sedang dimainkan. Jadi, gunakan
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
Jawaban panjang :
Warisan Prototip JavaScript
Juga ditempatkan di wiki AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Penting untuk terlebih dahulu memiliki pemahaman yang kuat tentang pewarisan prototypal, terutama jika Anda berasal dari latar belakang sisi server dan Anda lebih akrab dengan warisan klasik. Jadi mari kita tinjau dulu itu.
Misalkan parentScope memiliki properti aString, aNumber, anArray, anObject, dan aFunction. Jika childScope mewarisi secara prototipe dari parentScope, kami memiliki:
(Perhatikan bahwa untuk menghemat ruang, saya menunjukkan anArray
objek sebagai objek biru tunggal dengan tiga nilainya, daripada objek biru tunggal dengan tiga literal abu-abu yang terpisah.)
Jika kami mencoba mengakses properti yang ditentukan di parentScope dari cakupan anak, JavaScript pertama-tama akan melihat dalam lingkup anak, tidak menemukan properti, kemudian melihat dalam lingkup yang diwarisi, dan menemukan properti. (Jika tidak menemukan properti di parentScope, itu akan melanjutkan rantai prototipe ... sampai ke lingkup root). Jadi, ini semua benar:
childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'
Misalkan kita melakukan ini:
childScope.aString = 'child string'
Rantai prototipe tidak dikonsultasikan, dan properti aString baru ditambahkan ke childScope. Properti baru ini menyembunyikan / membayangi properti parentScope dengan nama yang sama. Ini akan menjadi sangat penting ketika kita membahas ng-repeat dan ng-include di bawah ini.
Misalkan kita melakukan ini:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
Rantai prototipe dikonsultasikan karena objek (anArray dan anObject) tidak ditemukan di childScope. Objek ditemukan di parentScope, dan nilai properti diperbarui pada objek asli. Tidak ada properti baru yang ditambahkan ke childScope; tidak ada objek baru yang dibuat. (Perhatikan bahwa dalam array dan fungsi JavaScript juga objek.)
Misalkan kita melakukan ini:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
Rantai prototipe tidak dikonsultasikan, dan ruang lingkup anak mendapat dua properti objek baru yang menyembunyikan / membayangi properti objek parentScope dengan nama yang sama.
Takeaways:
- Jika kita membaca childScope.propertyX, dan childScope memiliki propertyX, maka rantai prototipe tidak dikonsultasikan.
- Jika kita menetapkan childScope.propertyX, rantai prototipe tidak dikonsultasikan.
Satu skenario terakhir:
delete childScope.anArray
childScope.anArray[1] === 22 // true
Kami menghapus properti childScope terlebih dahulu, kemudian ketika kami mencoba mengakses properti lagi, rantai prototipe dikonsultasikan.
Warisan Lingkup Sudut
Peserta:
- Berikut ini membuat lingkup baru, dan mewarisi secara prototipe: ng-repeat, ng-include, ng-switch, ng-controller, direktif dengan
scope: true
, direktif dengan transclude: true
.
- Berikut ini menciptakan ruang lingkup baru yang tidak mewarisi secara prototipe: direktif dengan
scope: { ... }
. Ini menciptakan ruang lingkup "mengisolasi".
Catatan, secara default, arahan tidak membuat ruang lingkup baru - yaitu, defaultnya adalah scope: false
.
ng-sertakan
Misalkan kita ada di controller kita:
$scope.myPrimitive = 50;
$scope.myObject = {aNumber: 11};
Dan dalam HTML kami:
<script type="text/ng-template" id="/tpl1.html">
<input ng-model="myPrimitive">
</script>
<div ng-include src="'/tpl1.html'"></div>
<script type="text/ng-template" id="/tpl2.html">
<input ng-model="myObject.aNumber">
</script>
<div ng-include src="'/tpl2.html'"></div>
Setiap ng-include menghasilkan ruang lingkup anak baru, yang secara prototipe mewarisi dari ruang lingkup orang tua.
Mengetik (katakanlah, "77") ke dalam kotak teks input pertama menyebabkan lingkup anak untuk mendapatkan myPrimitive
properti lingkup baru yang menyembunyikan / bayangan properti lingkup induk dengan nama yang sama. Ini mungkin bukan yang Anda inginkan / harapkan.
Mengetik (katakanlah, "99") ke kotak teks input kedua tidak menghasilkan properti anak baru. Karena tpl2.html mengikat model ke properti objek, pewarisan prototipal muncul ketika ngModel mencari objek myObject - ia menemukannya dalam lingkup induk.
Kita dapat menulis ulang templat pertama yang menggunakan $ parent, jika kita tidak ingin mengubah model kita dari yang primitif ke objek:
<input ng-model="$parent.myPrimitive">
Mengetik (misalnya, "22") ke dalam kotak teks input ini tidak menghasilkan properti anak baru. Model sekarang terikat ke properti dari lingkup induk (karena $ parent adalah properti lingkup anak yang mereferensikan lingkup induk).
Untuk semua lingkup (prototipal atau tidak), Angular selalu melacak hubungan orangtua-anak (yaitu, hierarki), melalui properti lingkup $ parent, $$ childHead, dan $$ childTail. Saya biasanya tidak menunjukkan properti lingkup ini dalam diagram.
Untuk skenario di mana elemen bentuk tidak terlibat, solusi lain adalah mendefinisikan fungsi pada lingkup induk untuk memodifikasi primitif. Kemudian pastikan anak selalu memanggil fungsi ini, yang akan tersedia untuk ruang lingkup anak karena warisan prototypal. Misalnya,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
Berikut adalah contoh biola yang menggunakan pendekatan "fungsi orangtua" ini. (Biola ditulis sebagai bagian dari jawaban ini: https://stackoverflow.com/a/14104318/215945 .)
Lihat juga https://stackoverflow.com/a/13782671/215945 dan https://github.com/angular/angular.js/issues/1267 .
ng-switch
ng-switch lingkup warisan berfungsi seperti ng-include. Jadi, jika Anda memerlukan data 2 arah yang mengikat ke primitif di lingkup induk, gunakan $ parent, atau ubah model menjadi objek dan kemudian ikat ke properti objek itu. Ini akan menghindari menyembunyikan lingkup anak / membayangi properti lingkup orangtua.
Lihat juga AngularJS, ikat ruang lingkup switch-case?
ng-ulangi
Ng-repeat bekerja sedikit berbeda. Misalkan kita ada di controller kita:
$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects = [{num: 101}, {num: 202}]
Dan dalam HTML kami:
<ul><li ng-repeat="num in myArrayOfPrimitives">
<input ng-model="num">
</li>
<ul>
<ul><li ng-repeat="obj in myArrayOfObjects">
<input ng-model="obj.num">
</li>
<ul>
Untuk setiap item / iterasi, ng-repeat menciptakan ruang lingkup baru, yang secara purwarupa mewarisi dari ruang lingkup induk, tetapi juga menetapkan nilai item ke properti baru pada ruang lingkup anak baru . (Nama properti baru adalah nama variabel loop.) Inilah kode sumber Angular untuk ng-repeat sebenarnya:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
Jika item adalah primitif (seperti dalam myArrayOfPrimitive), pada dasarnya salinan nilai diberikan ke properti lingkup anak baru. Mengubah nilai properti lingkup anak (yaitu, menggunakan ng-model, maka lingkup anak num
) tidak mengubah array referensi lingkup induk. Jadi pada ng-repeat pertama di atas, setiap ruang lingkup anak mendapatkan num
properti yang independen dari array myArrayOfPrimitive:
Ng-repeat ini tidak akan berfungsi (seperti yang Anda inginkan / harapkan). Mengetik ke dalam kotak teks mengubah nilai dalam kotak abu-abu, yang hanya terlihat dalam cakupan anak. Apa yang kami inginkan adalah agar input memengaruhi array myArrayOfPrimitive, bukan properti primitif lingkup anak. Untuk mencapai ini, kita perlu mengubah model menjadi array objek.
Jadi, jika item adalah objek, referensi ke objek asli (bukan salinan) ditugaskan ke properti lingkup anak baru. Mengubah nilai properti lingkup anak (yaitu, menggunakan ng model, maka obj.num
) tidak mengubah objek lingkup referensi induk. Jadi pada ng-repeat kedua di atas, kita memiliki:
(Saya mewarnai satu garis abu-abu hanya supaya jelas ke mana ia pergi.)
Ini berfungsi seperti yang diharapkan. Mengetik ke dalam kotak teks mengubah nilai dalam kotak abu-abu, yang terlihat oleh cakupan anak dan orangtua.
Lihat juga Kesulitan dengan ng-model, ng-repeat, dan input dan
https://stackoverflow.com/a/13782671/215945
ng-controller
Pengendali yang bersarang menggunakan ng-controller menghasilkan warisan prototypal normal, seperti ng-include dan ng-switch, sehingga teknik yang sama berlaku. Namun, "ini dianggap sebagai bentuk buruk bagi dua pengendali untuk berbagi informasi melalui $ lingkup warisan" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
Layanan harus digunakan untuk berbagi data antara pengendali sebagai gantinya.
(Jika Anda benar-benar ingin berbagi data melalui warisan lingkup pengontrol, tidak ada yang perlu Anda lakukan. Ruang lingkup anak akan memiliki akses ke semua properti lingkup induk. Lihat juga Urutan beban pengontrol berbeda ketika memuat atau menavigasi )
arahan
- default (
scope: false
) - arahan tidak membuat ruang lingkup baru, jadi tidak ada warisan di sini. Ini mudah, tetapi juga berbahaya karena, misalnya, arahan mungkin berpikir itu menciptakan properti baru pada ruang lingkup, padahal sebenarnya itu clobber properti yang sudah ada. Ini bukan pilihan yang baik untuk arahan penulisan yang dimaksudkan sebagai komponen yang dapat digunakan kembali.
scope: true
- Arahan menciptakan ruang lingkup anak baru yang secara prototipe mewarisi dari ruang lingkup orang tua. Jika lebih dari satu arahan (pada elemen DOM yang sama) meminta ruang lingkup baru, hanya satu ruang lingkup anak baru dibuat. Karena kita memiliki pewarisan prototipal "normal", ini seperti ng-include dan ng-switch, jadi berhati-hatilah dengan pengikatan data 2 arah ke primitif cakupan orangtua, dan menyembunyikan anak / membayangi properti lingkup orangtua.
scope: { ... }
- Arahan menciptakan ruang lingkup isolat / terisolasi baru. Ini tidak mewarisi secara prototipe. Ini biasanya pilihan terbaik Anda saat membuat komponen yang dapat digunakan kembali, karena arahan tidak dapat secara tidak sengaja membaca atau memodifikasi lingkup induk. Namun, arahan tersebut sering membutuhkan akses ke beberapa properti lingkup induk. Hash objek digunakan untuk mengatur pengikatan dua arah (menggunakan '=') atau pengikatan satu arah (menggunakan '@') antara lingkup induk dan cakupan isolat. Ada juga '&' untuk mengikat ekspresi lingkup induk. Jadi, ini semua membuat properti lingkup lokal yang diturunkan dari lingkup induk. Perhatikan bahwa atribut digunakan untuk membantu mengatur pengikatan - Anda tidak bisa hanya merujuk nama properti lingkup induk dalam hash objek, Anda harus menggunakan atribut. Misalnya, ini tidak akan berfungsi jika Anda ingin mengikat ke properti indukparentProp
dalam ruang lingkup yang terisolasi: <div my-directive>
dan scope: { localProp: '@parentProp' }
. Atribut harus digunakan untuk menentukan setiap properti induk yang ingin dirujuk oleh direktif ke: <div my-directive the-Parent-Prop=parentProp>
dan scope: { localProp: '@theParentProp' }
.
Mengisolasi __proto__
Objek referensi lingkup . Isolate scope's $ parent mereferensikan lingkup parent, jadi meskipun ia diisolasi dan tidak mewarisi prototipically dari lingkup parent, itu masih lingkup anak.
Untuk gambar di bawah ini yang kami miliki
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
dan
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
Juga, asumsikan arahan melakukan ini dalam fungsi tautannya: scope.someIsolateProp = "I'm isolated"
Untuk informasi lebih lanjut tentang cakupan isolat lihat http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- arahan menciptakan ruang lingkup anak baru "ditransklusikan", yang secara purwarupa mewarisi dari ruang lingkup orang tua. Ruang lingkup ditransklusikan dan terisolasi (jika ada) adalah saudara kandung - properti $ parent dari setiap lingkup referensi lingkup induk yang sama. Ketika ada ruang lingkup yang ditransklusikan dan terisolasi, mengisolasi properti lingkup $$ nextSibling akan merujuk pada ruang lingkup yang ditransklusikan. Saya tidak mengetahui adanya nuansa dengan ruang lingkup yang ditransklusikan.
Untuk gambar di bawah ini, asumsikan arahan yang sama seperti di atas dengan tambahan ini:transclude: true
Biola ini memiliki showScope()
fungsi yang dapat digunakan untuk memeriksa ruang lingkup isolat dan transklusi. Lihat instruksi dalam komentar di biola.
Ringkasan
Ada empat jenis cakupan:
- pewarisan lingkup prototypal normal - ng-include, ng-switch, ng-controller, direktif dengan
scope: true
- pewarisan lingkup prototypal normal dengan salinan / penugasan - ng-repeat. Setiap iterasi dari ng-repeat menciptakan lingkup anak baru, dan bahwa lingkup anak baru selalu mendapatkan properti baru.
- mengisolasi ruang lingkup - direktif dengan
scope: {...}
. Yang ini bukan prototipe, tetapi '=', '@', dan '&' menyediakan mekanisme untuk mengakses properti lingkup induk, melalui atribut.
- lingkup yang ditransklusikan - direktif dengan
transclude: true
. Yang ini juga merupakan warisan normal lingkup prototipe, tetapi juga saudara kandung dari setiap lingkup isolasi.
Untuk semua lingkup (prototipal atau tidak), Angular selalu melacak hubungan orangtua-anak (yaitu, hierarki), melalui properti $ parent dan $$ childHead dan $$ childTail.
Diagram dihasilkan dengan graphvizFile "* .dot", yang ada di github . " Belajar JavaScript dengan Object Graphs " dari Tim Caswell adalah inspirasi untuk menggunakan GraphViz untuk diagram.