Pertanyaan Apa saja nuansa ruang lingkup prototipe / prototipikal warisan di AngularJS?


Itu Halaman Referensi Lingkup API mengatakan:

Ruang lingkup bisa mewarisi dari ruang lingkup orang tua.

Itu Halaman Cakupan Panduan Pengembang mengatakan:

Ruang lingkup (secara prototipe) mewarisi properti dari ruang lingkup induknya.

Jadi, apakah lingkup anak selalu secara prototipis mewarisi dari ruang lingkup orang tuanya? Apakah ada pengecualian? Ketika itu mewarisi, apakah itu selalu normal warisan prototipe JavaScript?


965
2017-12-27 04:48


asal


Jawaban:


Jawaban cepat:
Ruang lingkup anak biasanya secara prototip mewarisi dari ruang lingkup orang tuanya, tetapi tidak selalu. Satu pengecualian untuk aturan ini adalah arahan dengan scope: { ... } - ini menciptakan ruang lingkup "mengisolasi" yang tidak secara prototipikal mewarisi. Konstruk ini sering digunakan ketika membuat direktif "komponen yang dapat digunakan kembali".

Adapun nuansa, ruang lingkup warisan biasanya lurus ... sampai Anda butuhkan Pengikatan data 2 arah (yaitu, elemen formulir, ng-model) di ruang lingkup anak. Ng-repeat, ng-switch, dan ng-include dapat membuat Anda tersandung jika Anda mencoba untuk mengikat primitif (mis., angka, string, boolean) di ruang lingkup orang tua dari dalam lingkup anak. Itu tidak bekerja seperti yang diharapkan kebanyakan orang. Ruang lingkup anak mendapatkan properti sendiri yang menyembunyikan / membayangi properti induk dengan nama yang sama. Solusi Anda

  1. tentukan objek di induk untuk model Anda, kemudian referensi properti objek itu di anak: parentObj.someProp
  2. gunakan $ parent.parentScopeProperty (tidak selalu mungkin, tetapi lebih mudah dari 1. jika memungkinkan)
  3. mendefinisikan fungsi pada ruang lingkup orang tua, dan memanggilnya dari anak (tidak selalu mungkin)

Pengembang AngularJS baru sering tidak menyadari hal itu ng-repeat, ng-switch, ng-view, ng-include dan ng-if semua membuat lingkup anak baru, sehingga masalah sering muncul ketika arahan ini dilibatkan. (Lihat contoh ini untuk ilustrasi singkat masalah.)

Masalah ini dengan primitif dapat dengan mudah dihindari dengan mengikuti "praktik terbaik" dari selalu punya '.' di ng-model Anda - Menonton 3 menit layak. Misko menunjukkan masalah mengikat primitif dengan ng-switch.

Memiliki sebuah '.' dalam model Anda akan memastikan bahwa warisan prototipalnya sedang dimainkan. Jadi, gunakan

<input type="text" ng-model="someObj.prop1">

<!--rather than
<input type="text" ng-model="prop1">`
-->


Jawaban panjang:

Warisan Prototypal JavaScript

Juga ditempatkan di wiki AngularJS:  https://github.com/angular/angular.js/wiki/Understanding-Scopes

Penting untuk terlebih dahulu memiliki pemahaman yang kuat tentang warisan prototipe, terutama jika Anda berasal dari latar belakang sisi server dan Anda lebih akrab dengan warisan kelas-ical. Jadi mari kita ulas dulu.

Misalkan parentScope memiliki properti aString, aNumber, anArray, anObject, dan aFunction. Jika childScope secara prototip mewarisi dari parentScope, kita memiliki:

prototypal inheritance

(Perhatikan bahwa untuk menghemat ruang, saya menunjukkan anArray objek sebagai objek biru tunggal dengan tiga nilai, bukan objek biru tunggal dengan tiga literal abu-abu terpisah.)

Jika kita mencoba untuk mengakses properti yang didefinisikan pada parentScope dari lingkup anak, JavaScript akan terlebih dahulu melihat di ruang lingkup anak, tidak menemukan properti, lalu melihat di ruang lingkup yang diwariskan, dan menemukan properti. (Jika tidak menemukan properti di parentScope, itu akan melanjutkan rantai prototipe ... sepanjang jalan sampai ke ruang 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 kemudian melakukan ini:

childScope.aString = 'child string'

Rantai prototipe tidak dikonsultasikan, dan properti aString baru ditambahkan ke childScope. Properti baru ini menyembunyikan / bayangan properti parentScope dengan nama yang sama.  Ini akan menjadi sangat penting ketika kita membahas ng-repeat dan ng-include di bawah ini.

property hiding

Misalkan kita kemudian 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 larik dan fungsi JavaScript juga merupakan objek.)

follow the prototype chain

Misalkan kita kemudian melakukan ini:

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

Rantai prototipe tidak dikonsultasikan, dan lingkup anak mendapat dua properti objek baru yang menyembunyikan / bayangan properti objek indukScope dengan nama yang sama.

more property hiding

Takeaways:

  • Jika kita membaca childScope.propertyX, dan childScope memiliki propertyX, maka rantai prototipe tidak dikonsultasikan.
  • Jika kami 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.

after removing a child property


Peninggalan Angular Scope

Para pesaing:

  • Berikut ini menciptakan lingkup baru, dan mewarisi prototipe: ng-repeat, ng-include, ng-switch, ng-controller, directive dengan scope: true, direktif dengan transclude: true.
  • Berikut ini menciptakan ruang lingkup baru yang tidak mewarisi prototipe: direktif dengan scope: { ... }. Ini menciptakan ruang lingkup "mengisolasi" sebagai gantinya.

Perhatikan, secara default, arahan tidak membuat ruang lingkup baru - yaitu, defaultnya adalah scope: false.

ng-include

Misalkan kita ada di controller kami:

$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

Dan di 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-termasuk menghasilkan lingkup anak baru, yang secara prototipis mewarisi dari ruang lingkup orang tua.

ng-include child scopes

Mengetik (misalnya, "77") ke kotak teks masukan pertama menyebabkan ruang lingkup anak untuk mendapatkan yang baru myPrimitive lingkup properti yang menyembunyikan / bayangan properti lingkup induk dengan nama yang sama. Ini mungkin bukan yang Anda inginkan / harapkan.

ng-include with a primitive

Mengetik (misalnya, "99") ke dalam kotak teks masukan kedua tidak menghasilkan properti anak baru. Karena tpl2.html mengikat model ke properti objek, warisan prototipal akan muncul ketika ngModel mencari objek myObject - ia menemukannya dalam lingkup induk.

ng-include with an object

Kita dapat menulis ulang template pertama untuk menggunakan $ parent, jika kita tidak ingin mengubah model kita dari yang primitif menjadi objek:

<input ng-model="$parent.myPrimitive">

Mengetik (misalnya, "22") ke dalam kotak teks masukan ini tidak menghasilkan properti anak baru. Model ini sekarang terikat pada properti dari ruang lingkup induk (karena $ parent adalah properti lingkup anak yang mereferensi ruang lingkup orang tua).

ng-include with $parent

Untuk semua cakupan (prototipe atau tidak), Sudut selalu melacak hubungan orangtua-anak (yaitu, hierarki), melalui properti cakupan $ induk, $$ childHead dan $$ childTail. Saya biasanya tidak menunjukkan properti lingkup ini dalam diagram.

Untuk skenario di mana elemen form tidak terlibat, solusi lain adalah mendefinisikan fungsi pada ruang lingkup induk untuk memodifikasi primitif. Kemudian pastikan anak selalu memanggil fungsi ini, yang akan tersedia untuk lingkup anak karena warisan prototip. Misalnya.,

// in the parent scope
$scope.setMyPrimitive = function(value) {
     $scope.myPrimitive = value;
}

Ini dia contoh biola yang menggunakan pendekatan "fungsi orang tua" ini. (Bias 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 scope inheritance berfungsi seperti ng-include. Jadi jika Anda membutuhkan data 2-arah yang mengikat ke primitif dalam ruang lingkup orang tua, gunakan $ parent, atau ubah model untuk menjadi objek dan kemudian ikat ke properti objek tersebut. Ini akan menghindari lingkup anak yang menyembunyikan / membayangi properti ruang lingkup orang tua.

Lihat juga AngularJS, bind scope dari switch-case?

ng-ulang

Ng-repeat bekerja sedikit berbeda. Misalkan kita ada di controller kami:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]

Dan di 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 membuat cakupan baru, yang secara prototipis mewarisi dari ruang lingkup induk, tetapi juga menetapkan nilai item ke properti baru pada lingkup anak baru. (Nama properti baru adalah nama variabel loop.) Berikut adalah kode sumber Angular untuk ng-repeat sebenarnya adalah:

childScope = scope.$new();  // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value;  // creates a new childScope property

Jika item adalah primitif (seperti dalam myArrayOfPrimitives), pada dasarnya salinan nilai ditetapkan ke properti lingkup anak baru. Mengubah nilai properti ruang lingkup anak (yaitu, menggunakan model-ng, maka ruang lingkup anak num) tidak tidak mengubah array referensi ruang lingkup induk. Jadi pada ng-repeat pertama di atas, setiap lingkup anak mendapat a num properti yang tidak bergantung pada array myArrayOfPrimitives:

ng-repeat with primitives

Pengulangan-ng ini tidak akan berfungsi (seperti yang Anda inginkan / harapkan). Mengetik ke kotak teks mengubah nilai di kotak abu-abu, yang hanya terlihat di lingkup anak. Yang kami inginkan adalah input untuk mempengaruhi array myArrayOfPrimitives, bukan ruang lingkup anak properti primitif. 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 ruang lingkup anak (yaitu, menggunakan model-ng, karenanya obj.num) tidak mengubah objek referensi lingkup induk. Jadi pada ng-repeat kedua di atas, kita memiliki:

ng-repeat with objects

(Saya hanya memberi warna satu garis abu-abu 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 orang tua.

Lihat juga Kesulitan dengan ng-model, ng-repeat, dan input dan https://stackoverflow.com/a/13782671/215945

ng-controller

Pengontrol Nesting menggunakan hasil ng-controller dalam warisan prototipe yang normal, seperti ng-include dan ng-switch, jadi teknik yang sama berlaku. Namun, "itu dianggap bentuk buruk untuk dua kontroler untuk berbagi informasi melalui warisan lingkup $" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Sebuah layanan harus digunakan untuk berbagi data antar pengontrol.

(Jika Anda benar-benar ingin berbagi data melalui pengontrol lingkup warisan, tidak ada yang perlu Anda lakukan. Cakupan anak akan memiliki akses ke semua properti lingkup induk. Lihat juga Urutan pemuatan pengontrol berbeda saat memuat atau menavigasi)

arahan

  1. bawaan (scope: false) - direktif tidak membuat ruang lingkup baru, jadi tidak ada warisan di sini. Ini mudah, tetapi juga berbahaya karena, misalnya, arahan mungkin mengira itu adalah menciptakan properti baru di ruang lingkup, padahal sebenarnya itu adalah clobbering properti yang sudah ada. Ini bukan pilihan yang baik untuk menulis arahan yang dimaksudkan sebagai komponen yang dapat digunakan kembali.
  2. scope: true - Direktif menciptakan ruang lingkup anak baru yang secara prototipis mewarisi dari ruang lingkup orang tua. Jika lebih dari satu arahan (pada elemen DOM yang sama) meminta cakupan baru, hanya satu lingkup anak baru yang dibuat. Karena kita memiliki warisan prototipe "normal", ini seperti ng-include dan ng-switch, jadi waspada terhadap pengikatan data 2 arah ke primitif lingkup induk, dan lingkup anak yang menyembunyikan / membayangi properti lingkup induk.
  3. scope: { ... } - Direktif menciptakan ruang isolasi / terisolasi baru. Itu tidak secara prototipikal mewarisi. Ini biasanya pilihan terbaik Anda saat membuat komponen yang dapat digunakan kembali, karena direktif tidak dapat secara tidak sengaja membaca atau memodifikasi ruang lingkup orang tua. Namun, arahan seperti itu sering membutuhkan akses ke beberapa properti lingkup orangtua. Hash objek digunakan untuk mengatur pengikatan dua arah (menggunakan '=') atau pengikatan satu arah (menggunakan '@') antara ruang lingkup induk dan ruang lingkup isolasi. Ada juga '&' untuk mengikat ekspresi ruang lingkup orang tua. Jadi, ini semua membuat properti cakupan lokal yang berasal dari ruang lingkup orang tua. Perhatikan bahwa atribut digunakan untuk membantu mengatur pengikatan - Anda tidak bisa hanya mereferensikan nama properti lingkup orangtua dalam hash objek, Anda harus menggunakan atribut. Misalnya, ini tidak akan berfungsi jika Anda ingin mengikat ke properti induk parentProp dalam ruang lingkup yang terisolasi: <div my-directive> dan scope: { localProp: '@parentProp' }. Atribut harus digunakan untuk menentukan setiap properti induk yang direktif ingin mengikat: <div my-directive the-Parent-Prop=parentProp> dan scope: { localProp: '@theParentProp' }.
    Isolasi ruang lingkup __proto__ Objek referensi. Mengisolasi lingkup $ parent referensi ruang lingkup orang tua, jadi meskipun terisolasi dan tidak mewarisi prototipe dari ruang lingkup orang tua, itu masih ruang lingkup anak.
    Untuk gambar di bawah ini kami punya
      <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"
      isolated scope
    Untuk informasi lebih lanjut tentang lingkup isolasi lihat http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true- direktif menciptakan lingkup anak "ditransklusikan" baru, yang secara prototipis mewarisi dari ruang lingkup orang tua. Ruang lingkup yang dikecualikan dan yang terisolasi (jika ada) adalah saudara kandung - properti $ induk dari masing-masing lingkup referensi lingkup induk yang sama. Ketika ruang lingkup yang terisolasi dan terisolasi keduanya ada, isolasi ruang lingkup $$ berikutnyaSibling akan merujuk pada ruang lingkup yang dikecualikan. Saya tidak menyadari adanya nuansa dengan ruang lingkup yang dikecualikan.
    Untuk gambar di bawah ini, asumsikan direktif yang sama seperti di atas dengan tambahan ini: transclude: true
    transcluded scope

Ini biola mempunyai sebuah showScope() fungsi yang dapat digunakan untuk memeriksa ruang lingkup yang terisolasi dan ditransklusikan. Lihat instruksi di komentar di biola.


Ringkasan

Ada empat tipe cakupan:

  1. ruang lingkup prototipe yang normal - ng-include, ng-switch, ng-controller, direktif dengan scope: true
  2. ruang lingkup prototipe yang normal dengan copy / tugas - ng-repeat. Setiap iterasi ng-repeat menciptakan lingkup anak baru, dan ruang lingkup anak baru selalu mendapat properti baru.
  3. ruang lingkup isolasi - direktif dengan scope: {...}. Ini bukan prototipe, tetapi '=', '@', dan '&' menyediakan mekanisme untuk mengakses properti lingkup orangtua, melalui atribut.
  4. ruang lingkup yang dikecualikan - direktif dengan transclude: true. Yang satu ini juga merupakan ruang lingkup prototipe yang normal, tetapi juga merupakan saudara dari ruang lingkup isolasi.

Untuk semua cakupan (prototipe atau tidak), Sudut selalu melacak hubungan orangtua-anak (yaitu, hierarki), melalui properti $ induk dan $$ childHead dan $$ childTail.

Diagram dibuat dengan  File "* .dot", yang aktif github. Tim Caswell "Belajar JavaScript dengan Grafik Objek"adalah inspirasi untuk menggunakan GraphViz untuk diagram.


1694
2017-12-27 04:48



Saya sama sekali tidak ingin bersaing dengan jawaban Mark, tetapi hanya ingin menyoroti bagian yang akhirnya membuat semuanya klik sebagai orang baru Warisan Javascript dan rantai prototipenya.

Hanya properti yang membaca mencari rantai prototipe, bukan menulis. Jadi ketika Anda mengatur

myObject.prop = '123';

Itu tidak mencari rantai, tetapi ketika Anda mengatur

myObject.myThing.prop = '123';

ada bacaan halus yang terjadi dalam operasi penulisan itu yang mencoba mencari myThing sebelum menulis ke prop. Jadi itulah mengapa menulis ke object.properties dari anak mendapat objek orang tua.


135
2018-05-16 15:22



Saya ingin menambahkan contoh warisan prototipikal dengan javascript ke jawaban @Scott Driscoll. Kami akan menggunakan pola pewarisan klasik dengan Object.create () yang merupakan bagian dari spesifikasi EcmaScript 5.

Pertama kita membuat fungsi objek "Induk"

function Parent(){

}

Kemudian tambahkan prototipe ke fungsi objek "Induk"

 Parent.prototype = {
 primitive : 1,
 object : {
    one : 1
   }
}

Buat fungsi objek "Anak"

function Child(){

}

Tentukan prototipe anak (Membuat prototipe anak mewarisi dari prototipe induk)

Child.prototype = Object.create(Parent.prototype);

Tetapkan konstruktor prototipe "Anak" yang tepat

Child.prototype.constructor = Child;

Tambahkan metode "changeProps" ke prototipe anak, yang akan menulis ulang nilai properti "primitif" di objek Child dan ubah nilai "object.one" baik pada objek Child dan Parent

Child.prototype.changeProps = function(){
    this.primitive = 2;
    this.object.one = 2;
};

Memulai objek Orangtua (ayah) dan Anak (anak).

var dad = new Parent();
var son = new Child();

Metode panggilan Anak (anak) changeProps

son.changeProps();

Periksa hasilnya.

Properti primitif orangtua tidak berubah

console.log(dad.primitive); /* 1 */

Properti primitif anak berubah (ditulis ulang)

console.log(son.primitive); /* 2 */

Induk dan Anak objek.satu properti berubah

console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */

Contoh kerja di sini http://jsbin.com/xexurukiso/1/edit/

Info lebih lanjut tentang Object.create di sini https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create


18
2017-11-08 22:45