Schnelle Antwort :
Ein untergeordneter Bereich erbt normalerweise prototypisch von seinem übergeordneten Bereich, jedoch nicht immer. Eine Ausnahme von dieser Regel ist eine Direktive mit scope: { ... }
- dies schafft einen "isolierten" Bereich, der nicht prototypisch erbt. Dieses Konstrukt wird häufig beim Erstellen einer Direktive für "wiederverwendbare Komponenten" verwendet.
Was die Nuancen betrifft, ist die Bereichsvererbung normalerweise unkompliziert ... bis Sie im untergeordneten Bereich eine bidirektionale Datenbindung (dh Formularelemente, ng-Modell) benötigen . Ng-repeat, ng-switch und ng-include können Sie auslösen, wenn Sie versuchen, innerhalb des untergeordneten Bereichs an ein Grundelement (z. B. Zahl, Zeichenfolge, Boolescher Wert) im übergeordneten Bereich zu binden . Es funktioniert nicht so, wie die meisten Leute erwarten, dass es funktionieren sollte. Der untergeordnete Bereich erhält eine eigene Eigenschaft, die die übergeordnete Eigenschaft mit demselben Namen verbirgt / schattiert. Ihre Problemumgehungen sind
- Definieren Sie Objekte im übergeordneten Element für Ihr Modell und verweisen Sie dann auf eine Eigenschaft dieses Objekts im untergeordneten Element: parentObj.someProp
- benutze $ parent.parentScopeProperty (nicht immer möglich, aber einfacher als 1. wo möglich)
- Definieren Sie eine Funktion im übergeordneten Bereich und rufen Sie sie vom untergeordneten Bereich aus auf (nicht immer möglich).
New AngularJS Entwickler oft nicht bewusst , dass ng-repeat
, ng-switch
, ng-view
, ng-include
und ng-if
alle neuen untergeordneten Bereiche schaffen, so oft das Problem zeigt sich , wenn diese Richtlinien beteiligt sind. (In diesem Beispiel wird das Problem kurz veranschaulicht.)
Dieses Problem mit Grundelementen kann leicht vermieden werden, indem die "Best Practice" befolgt wird, immer ein 'zu haben.' in deinen ng-modellen - schau dir 3 minuten an. Misko demonstriert das primitive Bindungsproblem mit ng-switch
.
Ein ... haben '.' In Ihren Modellen wird sichergestellt, dass die prototypische Vererbung im Spiel ist. Also, benutze
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
Lange Antwort :
Prototypische Vererbung von JavaScript
Ebenfalls im AngularJS-Wiki platziert: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Es ist wichtig, zunächst ein solides Verständnis der prototypischen Vererbung zu haben, insbesondere wenn Sie aus einem serverseitigen Hintergrund stammen und mit der klassischen Vererbung besser vertraut sind. Lassen Sie uns das zuerst überprüfen.
Angenommen, parentScope hat die Eigenschaften aString, aNumber, anArray, anObject und aFunction. Wenn childScope prototypisch von parentScope erbt, haben wir:
(Um Platz zu sparen, zeige ich das anArray
Objekt als einzelnes blaues Objekt mit seinen drei Werten und nicht als einzelnes blaues Objekt mit drei separaten grauen Literalen.)
Wenn wir versuchen, vom untergeordneten Bereich aus auf eine im parentScope definierte Eigenschaft zuzugreifen, sucht JavaScript zuerst im untergeordneten Bereich, nicht in der Eigenschaft, dann im geerbten Bereich und sucht die Eigenschaft. (Wenn die Eigenschaft im parentScope nicht gefunden wurde, wird die Prototypenkette bis zum Stammbereich fortgesetzt.) Das ist also alles wahr:
childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'
Angenommen, wir machen dann Folgendes:
childScope.aString = 'child string'
Die Prototypenkette wird nicht konsultiert, und dem childScope wird eine neue aString-Eigenschaft hinzugefügt. Diese neue Eigenschaft verbirgt / schattiert die gleichnamige parentScope-Eigenschaft. Dies wird sehr wichtig, wenn wir unten ng-repeat und ng-include diskutieren.
Angenommen, wir machen dann Folgendes:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
Die Prototypenkette wird konsultiert, da die Objekte (anArray und anObject) nicht im childScope gefunden werden. Die Objekte befinden sich im parentScope und die Eigenschaftswerte werden für die ursprünglichen Objekte aktualisiert. Dem childScope werden keine neuen Eigenschaften hinzugefügt. Es werden keine neuen Objekte erstellt. (Beachten Sie, dass in JavaScript Arrays und Funktionen auch Objekte sind.)
Angenommen, wir machen dann Folgendes:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
Die Prototypenkette wird nicht konsultiert, und der untergeordnete Bereich erhält zwei neue Objekteigenschaften, die die gleichnamigen parentScope-Objekteigenschaften ausblenden / schattieren.
Imbissbuden:
- Wenn wir childScope.propertyX lesen und childScope propertyX hat, wird die Prototypkette nicht konsultiert.
- Wenn wir childScope.propertyX festlegen, wird die Prototypenkette nicht konsultiert.
Ein letztes Szenario:
delete childScope.anArray
childScope.anArray[1] === 22 // true
Wir haben zuerst die childScope-Eigenschaft gelöscht. Wenn wir dann erneut versuchen, auf die Eigenschaft zuzugreifen, wird die Prototypenkette konsultiert.
Vererbung des Winkelbereichs
Die Anwärter:
- Die folgenden Elemente erstellen neue Bereiche und erben prototypisch: ng-repeat, ng-include, ng-switch, ng-controller, Direktive mit
scope: true
, Direktive mit transclude: true
.
- Im Folgenden wird ein neuer Bereich erstellt, der nicht prototypisch erbt: Direktive mit
scope: { ... }
. Dadurch wird stattdessen ein "isolierter" Bereich erstellt.
Beachten Sie, dass Direktiven standardmäßig keinen neuen Bereich erstellen, dh der Standardwert ist scope: false
.
ng-include
Angenommen, wir haben in unserem Controller:
$scope.myPrimitive = 50;
$scope.myObject = {aNumber: 11};
Und in unserem HTML:
<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>
Jedes ng-include generiert einen neuen untergeordneten Bereich, der prototypisch vom übergeordneten Bereich erbt.
Wenn Sie (z. B. "77") in das erste Eingabetextfeld eingeben, erhält der untergeordnete Bereich eine neue myPrimitive
Bereichseigenschaft, die die gleichnamige übergeordnete Bereichseigenschaft verbirgt / schattiert. Dies ist wahrscheinlich nicht das, was Sie wollen / erwarten.
Die Eingabe (z. B. "99") in das zweite Eingabetextfeld führt nicht zu einer neuen untergeordneten Eigenschaft. Da tpl2.html das Modell an eine Objekteigenschaft bindet, wird die prototypische Vererbung aktiviert, wenn das ngModel nach dem Objekt myObject sucht - es findet es im übergeordneten Bereich.
Wir können die erste Vorlage, die $ parent verwendet, neu schreiben, wenn wir unser Modell nicht von einem Grundelement in ein Objekt ändern möchten:
<input ng-model="$parent.myPrimitive">
Das Eingeben (z. B. "22") in dieses Eingabetextfeld führt nicht zu einer neuen untergeordneten Eigenschaft. Das Modell ist jetzt an eine Eigenschaft des übergeordneten Bereichs gebunden (da $ parent eine untergeordnete Bereichseigenschaft ist, die auf den übergeordneten Bereich verweist).
Für alle Bereiche (prototypisch oder nicht) verfolgt Angular immer eine Eltern-Kind-Beziehung (dh eine Hierarchie) über die Bereichseigenschaften $ parent, $$ childHead und $$ childTail. Normalerweise zeige ich diese Bereichseigenschaften nicht in den Diagrammen.
In Szenarien, in denen Formularelemente nicht beteiligt sind, besteht eine andere Lösung darin, eine Funktion im übergeordneten Bereich zu definieren, um das Grundelement zu ändern. Stellen Sie dann sicher, dass das untergeordnete Element immer diese Funktion aufruft, die dem untergeordneten Bereich aufgrund der prototypischen Vererbung zur Verfügung steht. Z.B,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
Hier ist ein Beispiel für eine Geige , die diesen Ansatz der "übergeordneten Funktion" verwendet. (Die Geige wurde als Teil dieser Antwort geschrieben: https://stackoverflow.com/a/14104318/215945 .)
Siehe auch https://stackoverflow.com/a/13782671/215945 und https://github.com/angular/angular.js/issues/1267 .
ng-Schalter
Die Vererbung des ng-switch-Bereichs funktioniert genauso wie ng-include. Wenn Sie also eine bidirektionale Datenbindung an ein Grundelement im übergeordneten Bereich benötigen, verwenden Sie $ parent oder ändern Sie das Modell in ein Objekt und binden Sie es dann an eine Eigenschaft dieses Objekts. Dadurch wird vermieden, dass untergeordnete Bereiche die Eigenschaften des übergeordneten Bereichs ausblenden / schattieren.
Siehe auch AngularJS, Bindungsbereich eines Switch-Gehäuses?
ng-wiederholen
Ng-Repeat funktioniert etwas anders. Angenommen, wir haben in unserem Controller:
$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects = [{num: 101}, {num: 202}]
Und in unserem HTML:
<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>
Für jedes Element / jede Iteration erstellt ng-repeat einen neuen Bereich, der prototypisch vom übergeordneten Bereich erbt, aber den Wert des Elements auch einer neuen Eigenschaft im neuen untergeordneten Bereich zuweist . (Der Name der neuen Eigenschaft ist der Name der Schleifenvariablen.) Der Angular-Quellcode für ng-repeat lautet eigentlich wie folgt:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
Wenn item ein Grundelement ist (wie in myArrayOfPrimitives), wird der neuen untergeordneten Bereichseigenschaft im Wesentlichen eine Kopie des Werts zugewiesen. Durch Ändern des Werts der Eigenschaft des untergeordneten Bereichs (dh unter Verwendung des ng-Modells, also des untergeordneten Bereichs num
) wird das Array, auf das der übergeordnete Bereich verweist, nicht geändert. In der ersten ng-Wiederholung oben erhält jeder num
untergeordnete Bereich eine Eigenschaft, die vom Array myArrayOfPrimitives unabhängig ist:
Diese ng-Wiederholung funktioniert nicht (wie Sie es wollen / erwarten). Durch Eingabe in die Textfelder werden die Werte in den grauen Feldern geändert, die nur in den untergeordneten Bereichen sichtbar sind. Wir möchten, dass sich die Eingaben auf das Array myArrayOfPrimitives auswirken, nicht auf eine primitive Eigenschaft des untergeordneten Bereichs. Um dies zu erreichen, müssen wir das Modell so ändern, dass es ein Array von Objekten ist.
Wenn das Element ein Objekt ist, wird der neuen untergeordneten Bereichseigenschaft ein Verweis auf das ursprüngliche Objekt (keine Kopie) zugewiesen. Durch Ändern des Werts der Eigenschaft des untergeordneten Bereichs (dh unter Verwendung von ng-model obj.num
) wird das Objekt geändert, auf das der übergeordnete Bereich verweist. In der zweiten ng-Wiederholung oben haben wir also:
(Ich habe eine Linie grau gefärbt, damit klar ist, wohin sie führt.)
Dies funktioniert wie erwartet. Durch Eingabe in die Textfelder werden die Werte in den grauen Feldern geändert, die sowohl für den untergeordneten als auch für den übergeordneten Bereich sichtbar sind.
Siehe auch Schwierigkeiten mit ng-Modell, ng-Wiederholung und Eingaben und
https://stackoverflow.com/a/13782671/215945
ng-controller
Das Verschachteln von Controllern, die ng-controller verwenden, führt zu einer normalen prototypischen Vererbung, genau wie ng-include und ng-switch. Daher gelten dieselben Techniken. "Es wird jedoch als schlechte Form angesehen, wenn zwei Controller Informationen über die Vererbung von $ scope austauschen." - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
Ein Dienst sollte zum Teilen von Daten zwischen verwendet werden Controller stattdessen.
(Wenn Sie Daten wirklich über die Vererbung des Controller-Bereichs freigeben möchten, müssen Sie nichts tun. Der untergeordnete Bereich hat Zugriff auf alle Eigenschaften des übergeordneten Bereichs. Siehe auch Die Ladereihenfolge des Controllers unterscheidet sich beim Laden oder Navigieren. )
Richtlinien
- default (
scope: false
) - Die Direktive erstellt keinen neuen Bereich, daher gibt es hier keine Vererbung. Dies ist einfach, aber auch gefährlich, da beispielsweise eine Direktive den Eindruck erweckt, dass eine neue Eigenschaft im Bereich erstellt wird, obwohl tatsächlich eine vorhandene Eigenschaft überlastet wird. Dies ist keine gute Wahl, um Anweisungen zu schreiben, die als wiederverwendbare Komponenten gedacht sind.
scope: true
- Die Direktive erstellt einen neuen untergeordneten Bereich, der prototypisch vom übergeordneten Bereich erbt. Wenn mehr als eine Direktive (für dasselbe DOM-Element) einen neuen Bereich anfordert, wird nur ein neuer untergeordneter Bereich erstellt. Da wir eine "normale" prototypische Vererbung haben, ist dies wie ng-include und ng-switch. Seien Sie also vorsichtig bei der bidirektionalen Datenbindung an Grundelemente des übergeordneten Bereichs und beim Ausblenden / Abschatten der Eigenschaften des übergeordneten Bereichs durch den untergeordneten Bereich.
scope: { ... }
- Die Richtlinie schafft einen neuen isolierten / isolierten Bereich. Es erbt nicht prototypisch. Dies ist normalerweise die beste Wahl, wenn Sie wiederverwendbare Komponenten erstellen, da die Direktive den übergeordneten Bereich nicht versehentlich lesen oder ändern kann. Solche Anweisungen benötigen jedoch häufig Zugriff auf einige übergeordnete Bereichseigenschaften. Der Objekt-Hash wird verwendet, um eine bidirektionale Bindung (mit '=') oder eine einseitige Bindung (mit '@') zwischen dem übergeordneten Bereich und dem isolierten Bereich einzurichten. Es gibt auch '&' zum Binden an übergeordnete Bereichsausdrücke. Diese alle erstellen also lokale Bereichseigenschaften, die vom übergeordneten Bereich abgeleitet sind. Beachten Sie, dass Attribute zum Einrichten der Bindung verwendet werden. Sie können nicht nur auf die Namen der übergeordneten Bereichseigenschaften im Objekt-Hash verweisen, sondern müssen ein Attribut verwenden. Dies funktioniert beispielsweise nicht, wenn Sie an die übergeordnete Eigenschaft binden möchtenparentProp
im isolierten Bereich: <div my-directive>
und scope: { localProp: '@parentProp' }
. Ein Attribut muss verwendet werden, um jede übergeordnete Eigenschaft anzugeben, an die die Direktive binden möchte: <div my-directive the-Parent-Prop=parentProp>
und scope: { localProp: '@theParentProp' }
.
Isolieren Sie die __proto__
Referenzen des Bereichs . $ Parent des isolierten Bereichs verweist auf den übergeordneten Bereich. Obwohl er isoliert ist und nicht prototypisch vom übergeordneten Bereich erbt, handelt es sich dennoch um einen untergeordneten Bereich.
Für das Bild unten haben wir
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
und nehmen
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
außerdem an, dass die Direktive dies in ihrer Verknüpfungsfunktion tut: scope.someIsolateProp = "I'm isolated"
Weitere Informationen zu isolierten Bereichen finden Sie unter http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- Die Richtlinie erstellt einen neuen "transkludierten" untergeordneten Bereich, der prototypisch vom übergeordneten Bereich erbt. Der transkludierte und der isolierte Bereich (falls vorhanden) sind Geschwister - die $ parent-Eigenschaft jedes Bereichs verweist auf denselben übergeordneten Bereich. Wenn sowohl ein transkludierter als auch ein isolierter Bereich vorhanden sind, verweist die isolierte Bereichseigenschaft $$ nextSibling auf den transkludierten Bereich. Mir sind keine Nuancen mit dem übertragenen Umfang bekannt.
Nehmen Sie für das Bild unten dieselbe Direktive wie oben mit diesem Zusatz an:transclude: true
Diese Geige hat eine showScope()
Funktion, mit der ein isoliertes und ein ausgeschlossenes Zielfernrohr untersucht werden kann. Siehe die Anweisungen in den Kommentaren in der Geige.
Zusammenfassung
Es gibt vier Arten von Bereichen:
- normale Vererbung des prototypischen Bereichs - ng-include, ng-switch, ng-controller, Direktive mit
scope: true
- normale Vererbung des prototypischen Bereichs mit einer Kopie / Zuweisung - ng-repeat. Jede Iteration von ng-repeat erstellt einen neuen untergeordneten Bereich, und dieser neue untergeordnete Bereich erhält immer eine neue Eigenschaft.
- Umfang isolieren - Direktive mit
scope: {...}
. Dieser ist nicht prototypisch, aber '=', '@' und '&' bieten einen Mechanismus für den Zugriff auf Eigenschaften des übergeordneten Bereichs über Attribute.
- ausgeschlossener Geltungsbereich - Richtlinie mit
transclude: true
. Dies ist auch eine normale Vererbung des prototypischen Bereichs, aber es ist auch ein Geschwister eines jeden isolierten Bereichs.
Für alle Bereiche (prototypisch oder nicht) verfolgt Angular immer eine Eltern-Kind-Beziehung (dh eine Hierarchie) über die Eigenschaften $ parent und $$ childHead und $$ childTail.
Diagramme wurden mit erstellt graphviz"* .dot" -Dateien, die sich auf Github befinden . Tim Caswells " Lernen von JavaScript mit Objektgraphen" war die Inspiration für die Verwendung von GraphViz für die Diagramme.