Jakie są niuanse zakresu dziedziczenia prototypowego / prototypowego w AngularJS?

Strona API Reference Scope mówi:

Zakres Może dziedziczyć z zakresu nadrzędnego.

The Developer Guide Scope page says:

Zakres (prototypowo) dziedziczy właściwości z zakresu nadrzędnego.

Czy zakres dziecka zawsze dziedziczy prototypowo po zakresie rodzica? Czy są wyjątki? Kiedy dziedziczy, Czy zawsze jest to normalne dziedziczenie prototypowe JavaScript?

Author: Peter Mortensen, 2012-12-27

3 answers

Szybka odpowiedź :
Zakres potomny Zwykle dziedziczy prototypowo po zakresie rodzica, ale nie zawsze. Jednym z wyjątków od tej reguły jest dyrektywa z scope: { ... } -- tworzy ona "izolowany" zakres, który nie dziedziczy prototypowo. Konstrukcja ta jest często używana przy tworzeniu dyrektywy "komponentu wielokrotnego użytku".

Jeśli chodzi o niuanse, dziedziczenie zakresu jest zwykle proste... dopóki nie potrzebujesz 2-way data binding (tj. Elementy formularza, ng-model) w dziecięcy zakres. Ng-repeat, ng-switch I ng-include mogą Cię potknąć, jeśli spróbujesz powiązać z prymitywnym (np. number, string, boolean) w zakresie nadrzędnym z wewnątrz zakresu podrzędnego. To nie działa tak, jak większość ludzi oczekuje, że powinno działać. Zakres potomny otrzymuje własną właściwość, która ukrywa / zacienia właściwość nadrzędną o tej samej nazwie. Twoje obejścia to

  1. zdefiniuj obiekty w rodzicu dla modelu, a następnie odwołaj się do właściwości tego obiektu w dziecku: parentObj.someProp
  2. użyj $ rodzica.parentScopeProperty (nie zawsze możliwe, ale łatwiejsze niż 1. gdzie to możliwe)
  3. Nie zawsze możliwe jest zdefiniowanie funkcji w zakresie nadrzędnym i wywołanie jej z poziomu potomka.]}

Nowi Programiści AngularJS często nie zdają sobie sprawy, że ng-repeat, ng-switch, ng-view, ng-include i ng-if Wszystkie tworzą nowe zakresy potomne, więc problem często pojawia się, gdy te dyrektywy są zaangażowane. (Zobacz ten przykład aby uzyskać szybką ilustrację problem.)

Tego problemu z prymitywami można łatwo uniknąć, postępując zgodnie z" najlepszą praktyką "Zawsze miej"."w Twoim ng-models - oglądaj warte 3 minuty. Misko demonstruje prymitywną kwestię wiązania z ng-switch.

Having a '."w swoich modelach zapewni, że dziedziczenie prototypowe jest w grze. Więc użyj

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

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


L-o-N-G odpowiedź :

Dziedziczenie Prototypów JavaScript

Również umieszczone na kątach wiki: https://github.com/angular/angular.js/wiki/Understanding-Scopes

Ważne jest, aby najpierw mieć solidne zrozumienie dziedziczenia prototypowego, zwłaszcza jeśli pochodzisz z tła po stronie serwera i jesteś bardziej zaznajomiony z dziedziczeniem klasy-ical. Więc najpierw to przejrzyjmy.

Przypuśćmy, że parentScope ma właściwości: aString, aNumber, anArray, anObject i aFunction. Jeśli childScope prototypowo dziedziczy z parentScope, to mieć:

dziedziczenie prototypowe

(zauważ, że aby zaoszczędzić miejsce, pokazuję obiekt anArray jako pojedynczy niebieski obiekt z trzema wartościami, a nie pojedynczy niebieski obiekt z trzema oddzielnymi szarymi literałami.)

Jeśli spróbujemy uzyskać dostęp do właściwości zdefiniowanej na parentScope z zakresu podrzędnego, JavaScript najpierw zajrzy do zakresu podrzędnego, nie znajdzie właściwości, a następnie zajrzy do odziedziczonego zakresu i znajdzie właściwość. (Jeśli nie znajdzie właściwości w parentScope, będzie kontynuowana w górę łańcucha prototypów... aż do głównego zakresu). Tak więc wszystkie są prawdziwe:

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'

Załóżmy, że zrobimy to:

childScope.aString = 'child string'

Prototype chain nie jest konsultowany, a nowa właściwość astringowa jest dodawana do childScope. ta nowa właściwość ukrywa / zacienia właściwość parentScope o tej samej nazwie. stanie się to bardzo ważne, gdy omówimy ng-repeat I ng-include poniżej.

ukrywanie nieruchomości

Załóżmy, że zrobimy to:

childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'

The prototype chain jest sprawdzany, ponieważ obiekty (anArray i anObject) nie znajdują się w childScope. Obiekty znajdują się w obszarze parentScope, a wartości właściwości są aktualizowane na oryginalnych obiektach. Do okna potomnego nie są dodawane żadne nowe właściwości; nie są tworzone żadne nowe obiekty. (Zauważ, że w JavaScript tablice i funkcje są również obiektami.)

podążaj za łańcuchem prototypów

Załóżmy, że zrobimy to:

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

Prototype chain nie jest konsultowany, a child scope otrzymuje dwa nowe właściwości obiektu, które ukrywają / cienią właściwości obiektu parentScope o tych samych nazwach.

więcej ukrywania nieruchomości

  • jeśli odczytamy childScope.propertyX, a childScope ma propertyX, wtedy łańcuch prototypów nie jest konsultowany.
  • jeśli ustawimy childScope.propertyX, łańcuch prototypów nie jest konsultowany.

Ostatni scenariusz:

delete childScope.anArray
childScope.anArray[1] === 22  // true

Najpierw usunęliśmy właściwość childScope, a potem, gdy spróbujemy ponownie uzyskać dostęp do tej właściwości, prototype chain is consulted.

po usunięciu mienia dziecka


Dziedziczenie Zakresu Kątowego

Pretendenci:

    Ng-repeat, ng-include, ng-switch, ng-controller, directive with scope: true, directive with transclude: true.
  • następująca tworzy nowy zakres, który nie dziedziczy prototypowo: dyrektywa z scope: { ... }. Tworzy to zamiast tego obszar" izoluj".

Uwaga, Domyślnie, dyrektywy nie tworzą nowego zakresu -- tzn. domyślną wartością jest scope: false.

Ng-include

Załóżmy, że mamy w naszym kontrolerze:

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

I w naszym 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>

Każdy ng-include generuje nowy zakres potomny, który prototypowo dziedziczy z zakresu rodzica.

ng-obejmują lunety dziecięce

Wpisanie (powiedzmy "77") do pierwszego pola tekstowego powoduje, że zakres potomny otrzymuje nową właściwość myPrimitive scope, która ukrywa / zacienia nadrzędną właściwość scope o tej samej nazwie. To prawdopodobnie nie jest to, czego chcesz/oczekujesz.

ng-include z prymitywnym

Wpisanie (powiedzmy "99") do drugiego pola tekstowego nie powoduje powstania nowej właściwości potomnej. Ponieważ tpl2.html wiąże model z właściwością obiektu, dziedziczenie prototypowe rozpoczyna się, gdy ngModel szuka obiektu myObject -- znajduje go w obszarze nadrzędnym.

ng-include with an object

Możemy przepisać pierwszy szablon do użycia $ parent, jeśli nie chcemy zmieniać naszego modelu z prymitywnego na obiekt:

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

Wpisanie (powiedzmy "22") do tego pola tekstowego nie powoduje powstania nowej właściwości potomnej. Model jest teraz powiązany z właściwością zakresu nadrzędnego (ponieważ $parent jest właściwością zakresu podrzędnego, która odwołuje się do zakresu nadrzędnego).

ng-include z $ parent

Dla wszystkich zakresów (prototypowych lub nie), Angular zawsze śledzi relację rodzic-dziecko (np. hierarchię), za pomocą właściwości zakresu $parent, $ $ childHead i $$childTail. Normalnie nie pokazuję tych właściwości zakresu w Schematy.

Dla scenariuszy, w których elementy formularza nie są zaangażowane, innym rozwiązaniem jest zdefiniowanie funkcji w zakresie nadrzędnym w celu modyfikacji prymitywu. Następnie upewnij się, że dziecko zawsze wywołuje tę funkcję, która będzie dostępna dla zakresu dziecka ze względu na dziedziczenie prototypowe. Np.,

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

Oto przykładowe fiddle , które wykorzystuje to podejście "funkcji rodzicielskiej". (Skrzypek został napisany w ramach tej odpowiedzi: https://stackoverflow.com/a/14104318/215945.)

Zobacz też https://stackoverflow.com/a/13782671/215945 i https://github.com/angular/angular.js/issues/1267 .

Ng-switch

Dziedziczenie zakresu ng-switch działa tak samo jak ng-include. Jeśli więc potrzebujesz dwukierunkowego powiązania danych z prymitywem w obszarze nadrzędnym, użyj $parent, lub zmień model na obiekt, a następnie powiązaj z właściwością tego obiektu. Pozwoli to uniknąć zakresu dzieci ukrywanie / cieniowanie właściwości zakresu nadrzędnego.

Zobacz też AngularJS, bind scope of a switch-case?

Ng-repeat

Ng-repeat działa trochę inaczej. Załóżmy, że mamy w naszym kontrolerze:

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

I w naszym 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>

Dla każdego elementu / iteracji, ng-repeat tworzy nowy zakres, który prototypowo dziedziczy z zakresu nadrzędnego, , ale także przypisuje wartość elementu do nowej właściwości na nowym zakresie potomnym . (The nazwa nowej właściwości jest nazwą zmiennej pętli. Jest to kod źródłowy Angular dla ng-repeat:

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

Jeśli element jest prymitywny (jak w myArrayOfPrimitives), zasadniczo Kopia wartości jest przypisana do nowej właściwości child scope. Zmiana wartości właściwości zakresu potomnego (np. użycie ng-model, stąd zakres potomny num) powoduje , a nie zmianę tablicy, do której odwołuje się zakres nadrzędny. Tak więc w pierwszym ng-repeat powyżej, każdy zakres potomny otrzymuje num właściwość niezależna od tablicy myArrayOfPrimitives:

ng-repeat with primitives

To ng-repeat nie zadziała(tak jak tego chcesz / oczekujesz). Wpisanie do pól tekstowych zmienia wartości w szarych polach, które są widoczne tylko w zakresach podrzędnych. Chcemy, aby dane wejściowe wpływały na tablicę myArrayOfPrimitives, a nie na prymitywną właściwość zakresu podrzędnego. Aby to osiągnąć, musimy zmienić model na tablicę obiektów.

Więc jeśli element jest obiektem, a odniesienie do oryginalnego obiektu (Nie kopii) jest przypisane do nowej właściwości child scope. Zmiana wartości właściwości zakresu potomnego (np. za pomocą modelu ng, stąd obj.num) czy zmienia obiekt, do którego odwołuje się zakres nadrzędny. Tak więc w drugim ng-powtórzeniu powyżej mamy:

ng-powtarzanie z obiektami

(pokolorowałem jedną linię na szaro, żeby było jasne, dokąd zmierza.)

To działa zgodnie z oczekiwaniami. Wpisanie do pól tekstowych zmienia wartości w szarych polach, które są widoczne zarówno dla zakresu dziecka, jak i rodzica.

Zobacz także trudności z ng-model, ng-repeat i wejściami oraz https://stackoverflow.com/a/13782671/215945

Ng-kontroler

Zagnieżdżanie sterowników za pomocą ng-controller powoduje normalne dziedziczenie prototypów, tak jak ng-include I ng-switch, więc stosuje się te same techniki. Jednak " jest to uważane za złą formę dla dwóch kontrolerów do dzielenia się informacjami poprzez dziedziczenie $ scope" -- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture / Zamiast tego usługa powinna być używana do udostępniania danych między administratorami.

(jeśli naprawdę chcesz udostępniać dane poprzez dziedziczenie zakresu kontrolerów, nie musisz nic robić. Zakres potomny będzie miał dostęp do wszystkich właściwości zakresu nadrzędnego. Zobacz także kolejność ładowania kontrolera różni się podczas ładowania lub nawigacji )

Dyrektywy

  1. default (scope: false) - dyrektywa nie tworzy nowego zakresu, więc nie ma tu dziedziczenia. Jest to łatwe, ale również niebezpieczne, ponieważ np. dyrektywa może myśleć, że tworzy nową właściwość w zakresie, podczas gdy w rzeczywistości zatacza istniejącą właściwość. Nie jest to dobry wybór do pisania dyrektyw, które są przeznaczone jako komponenty wielokrotnego użytku.
  2. scope: true - dyrektywa tworzy nowy zakres podrzędny, który prototypowo dziedziczy po zakresie nadrzędnym. Jeśli więcej niż jedna dyrektywa (na tym samym elemencie DOM) żąda nowego zakresu, tworzony jest tylko jeden nowy zakres potomny. Ponieważ mamy "normalne" dziedziczenie prototypowe, jest to podobne do ng-include I ng-switch, więc należy uważać na dwukierunkowe powiązanie danych z nadrzędnymi podstawami zakresu i ukrywanie/cieniowanie zakresu podrzędnego właściwości.
  3. scope: { ... } - dyrektywa tworzy nowy izolowany/izolowany zakres. Nie dziedziczy prototypowo. Jest to zazwyczaj najlepszy wybór podczas tworzenia komponentów wielokrotnego użytku, ponieważ dyrektywa nie może przypadkowo odczytać lub zmodyfikuj zakres nadrzędny. Jednak takie dyrektywy często wymagają dostępu do kilku właściwości zakresu nadrzędnego. Obiekt hash jest używany do Ustawienia dwukierunkowego powiązania (za pomocą '=') lub jednokierunkowego powiązania (za pomocą '@') pomiędzy nadrzędnym zakresem a izolowanym zakresem. Istnieje również'&', aby powiązać z nadrzędnymi wyrażeniami zakresu. Tak więc wszystkie one tworzą właściwości lokalnego zakresu, które pochodzą z zakresu nadrzędnego. Zauważ, że atrybuty są używane do konfiguracji powiązania - nie można odwoływać się tylko do nazw właściwości zakresu nadrzędnego w obiekcie hash, musisz użyć atrybutu. Na przykład, to nie zadziała, jeśli chcesz powiązać właściwość nadrzędną parentProp w izolowanym zakresie: <div my-directive> i scope: { localProp: '@parentProp' }. Atrybut musi być użyty do określenia każdej właściwości nadrzędnej, z którą dyrektywa chce się powiązać: <div my-directive the-Parent-Prop=parentProp> i scope: { localProp: '@theParentProp' }.
    Wyizoluj obiekt odniesienia zakresu __proto__. $Parent isolate scope odwołuje się do zakresu nadrzędnego, więc chociaż jest on izolowany i nie dziedziczy prototypowo z zakresu nadrzędnego, nadal jest to zakres podrzędny.
    dla na poniższym zdjęciu mamy
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> oraz
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    Załóżmy również, że dyrektywa robi to w swojej funkcji łączącej: scope.someIsolateProp = "I'm isolated"
    zakres wyodrębniony
    aby uzyskać więcej informacji na temat zakresów izolowanych zobacz http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope /
  4. transclude: true - dyrektywa tworzy nowy "transkludowany" zakres potomny, który prototypowo dziedziczy po zakresie nadrzędnym. Transkludowany i izolowany zakres (jeśli istnieje) są rodzeństwem -- właściwością $ parent of każdy zakres odwołuje się do tego samego zakresu nadrzędnego. Gdy istnieje zarówno transkludowany, jak i izolowany zakres, właściwość isolate scope $ $ nextSibling odwoła się do transkludowanego zakresu. Nie znam żadnych niuansów z transcluded scope.
    na poniższym obrazku przyjmij tę samą dyrektywę co powyżej z tym dodatkiem: transclude: true
    transcluded scope

Ten fiddle ma showScope() funkcję, która może być użyta do zbadania izolowanego i transkludowanego zakresu. Zobacz instrukcje w komentarze w fiddle.


Podsumowanie

Istnieją cztery rodzaje lunet:

  1. normalne dziedziczenie zakresu prototypowego -- ng-include, ng-switch, ng-controller, directive with scope: true
  2. normalne dziedziczenie zakresu prototypowego z kopią / przypisaniem -- ng-repeat. Każda iteracja ng-repeat tworzy nowy zakres potomny, a ten nowy zakres potomny zawsze otrzymuje nową właściwość.
  3. izoluj zakres -- dyrektywy z scope: {...}. Ten nie jest prototypowy, ale '=', '@'i' & ' zapewniają mechanizm dostępu do właściwości zakresu nadrzędnego za pomocą atrybutów.
  4. transcluded scope -- directive with transclude: true. Ten jest również normalnym dziedziczeniem zakresu prototypowego, ale jest również rodzeństwem dowolnego izolowanego zakresu.

Dla wszystkich zakresów (prototypowych lub nie), Angular zawsze śledzi relację rodzic-dziecko (np. hierarchię), za pomocą właściwości $parent i $ $ childHead oraz $$childTail.

Wykresy zostały wygenerowane za pomocą graphviz "*.dot " plików, które znajdują się na github . Tim Caswell "Learning JavaScript with object Graphs " był inspiracją do użycia GraphViz dla diagramów.

 1707
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:49

W żaden sposób nie chcę konkurować z odpowiedzią marka, ale chciałem tylko podkreślić kawałek, który w końcu sprawił, że wszystko kliknęło jako ktoś nowy w dziedziczeniu Javascript i jego łańcuchu prototypów .

Tylko właściwość odczytuje przeszukiwanie łańcucha prototypów, a nie zapisuje. więc kiedy ustawisz

myObject.prop = '123';

Nie wygląda na łańcuch, ale gdy ustawisz

myObject.myThing.prop = '123';

Jest subtelny odczyt w ramach tej operacji zapisu , która próbuje sprawdzić moje rzeczy przed pisanie na swój rekwizyt. Dlatego pisanie do sprzeciwu.właściwości od dziecka pobiera się w obiektach rodzica.

 136
Author: Scott Driscoll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-17 12:09:38

Chciałbym dodać przykład prototypowego dziedziczenia z javascript do @Scott Driscoll answer. Użyjemy klasycznego wzorca dziedziczenia z obiektem.create (), która jest częścią specyfikacji EcmaScript 5.

Najpierw tworzymy funkcję obiektu "rodzica"

function Parent(){

}

Następnie dodaj prototyp do funkcji obiektu "rodzica"

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

Tworzenie funkcji obiektu "Child"

function Child(){

}

Assign child prototype (spraw, aby prototyp dziecka dziedziczył od rodzica prototyp)

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

Przypisanie właściwego konstruktora prototypu" dziecka "

Child.prototype.constructor = Child;

Dodaj metodę " changeProps "do prototypu potomnego, który przepisze" primitive "wartość właściwości w obiekcie potomnym i zmieni obiekt".jedna " wartość zarówno w obiektach potomnych, jak i nadrzędnych

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

Inicjowanie obiektów rodzica (ojca) i dziecka (syna).

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

Wywołanie metody Child (son) changeProps

son.changeProps();

Sprawdź wyniki.

Macierzysta własność prymitywna nie Zmień

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

Własność prymitywna dziecka zmieniona (przepisana)

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

Obiekt rodzica i dziecka.one properties changed

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

Przykład pracy tutaj http://jsbin.com/xexurukiso/1/edit/

Więcej informacji o obiekcie.Utwórz tutaj https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

 19
Author: tylik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-08 22:51:55