API Reference strona Zakres mówi:
Zakres może dziedziczyć z zakresu nadrzędnego.
Developer Guide strona Zakres mówi:
Zakres (prototypowo) dziedziczy właściwości z zakresu nadrzędnego.
- Czy zatem zakres potomny zawsze dziedziczy prototypowo po zakresie macierzystym?
- Czy są wyjątki?
- Kiedy dziedziczy, czy zawsze jest to normalne dziedziczenie prototypowe JavaScript?
javascript
angularjs
inheritance
prototype
prototypal-inheritance
Mark Rajcok
źródło
źródło
Odpowiedzi:
Szybka odpowiedź :
zakres potomny zwykle prototypowo dziedziczy po zakresie macierzystym, ale nie zawsze. Jedynym wyjątkiem od tej reguły jest dyrektywa z
scope: { ... }
- tworzy to zakres „izolowania”, który nie dziedziczy prototypowo. Ta konstrukcja jest często używana podczas tworzenia dyrektywy „komponent wielokrotnego użytku”.Jeśli chodzi o niuanse, dziedziczenie zakresu jest zwykle proste ... dopóki nie będzie potrzebne dwukierunkowe wiązanie danych (tj. Elementy formularza, model ng) w zasięgu potomnym. Ng-repeat, ng-switch i ng-include mogą cię wyzwolić, jeśli spróbujesz połączyć się z operacją podstawową (np. Liczbą, łańcuchem, wartością logiczną) w zakresie nadrzędnym z zakresu podrzędnego. Nie działa tak, jak większość ludzi się spodziewa. Zasięg podrzędny otrzymuje własną właściwość, która ukrywa / ukrywa właściwość nadrzędną o tej samej nazwie. Twoje obejścia są
Nowe angularjs deweloperzy często nie zdają sobie sprawy, że
ng-repeat
,ng-switch
,ng-view
,ng-include
ing-if
wszystkim tworzenie nowych zakresów podrzędnych, więc problem często pojawia się, gdy zaangażowane są te wytyczne. (Zobacz ten przykład, aby szybko zilustrować problem.)Tego problemu z prymitywami można łatwo uniknąć, postępując zgodnie z „najlepszymi praktykami” zawsze mieć „.” w swoich modelach ng - obejrzyj 3 minuty. Misko demonstruje pierwotny problem wiązania z
ng-switch
.Posiadające '.' w twoich modelach zapewni, że dziedzictwo prototypowe będzie w grze. Więc użyj
Długa odpowiedź :
JavaScript Prototypal Inheritance
Umieszczono również na wiki AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Ważne jest, aby najpierw dobrze zrozumieć dziedziczenie prototypów, szczególnie jeśli pochodzisz z tła po stronie serwera i jesteś bardziej zaznajomiony z dziedziczeniem klasowym. Sprawdźmy to najpierw.
Załóżmy, że parentScope ma właściwości aString, aNumber, anArray, anObject i aFunction. Jeśli childScope prototypowo dziedziczy po parentScope, mamy:
(Uwaga: aby zaoszczędzić miejsce, pokazuję
anArray
obiekt jako pojedynczy niebieski obiekt z jego trzema wartościami, a nie jako pojedynczy niebieski obiekt z trzema oddzielnymi szarymi literałami.)Jeśli spróbujemy uzyskać dostęp do właściwości zdefiniowanej w parentScope z zakresu potomnego, JavaScript najpierw zajmie się zasięgiem potomnym, nie znajdzie właściwości, a następnie przejdzie w dziedziczonym zakresie i znajdzie właściwość. (Gdyby nie znalazł właściwości w parentScope, kontynuowałby w górę łańcucha prototypów ... aż do zakresu głównego). Tak więc wszystkie są prawdziwe:
Załóżmy, że wtedy to robimy:
Łańcuch prototypów nie jest konsultowany, a nowa właściwość aString 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 poniżej powtórzenie i powtórzenie ng.
Załóżmy, że wtedy to robimy:
Sprawdzono łańcuch prototypów, ponieważ obiektów (anArray i anObject) nie znaleziono w childScope. Obiekty znajdują się w parentScope, a wartości właściwości są aktualizowane na oryginalnych obiektach. Żadne nowe właściwości nie są dodawane do childScope; nie są tworzone nowe obiekty. (Pamiętaj, że w JavaScript tablice i funkcje są również obiektami).
Załóżmy, że wtedy to robimy:
Łańcuch prototypów nie jest konsultowany, a zakres potomny otrzymuje dwie nowe właściwości obiektu, które ukrywają / cień właściwości obiektu parentScope o tych samych nazwach.
Na wynos:
Ostatni scenariusz:
Najpierw usunęliśmy właściwość childScope, a następnie, gdy próbujemy ponownie uzyskać dostęp do właściwości, sprawdzany jest łańcuch prototypów.
Dziedziczenie zakresu kątowego
Uczestnicy:
scope: true
, dyrektywa ztransclude: true
.scope: { ... }
. To tworzy zamiast tego zakres „izoluj”.Zauważ, że domyślnie dyrektywy nie tworzą nowego zakresu - tzn. Domyślnie jest to
scope: false
.ng-include
Załóżmy, że mamy w naszym kontrolerze:
A w naszym HTML:
Każde dołączenie ng generuje nowy zakres potomny, który prototypowo dziedziczy z zakresu macierzystego.
Wpisanie (powiedzmy „77”) w pierwszym wejściowym polu tekstowym powoduje, że zakres potomny otrzymuje nową
myPrimitive
właściwość scope, która ukrywa / ukrywa właściwość zakresu nadrzędnego o tej samej nazwie. Prawdopodobnie nie jest to, czego chcesz / oczekujesz.Wpisanie (powiedzmy „99”) w drugim wejściowym polu tekstowym 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 zakresie nadrzędnym.
Możemy przepisać pierwszy szablon, aby użyć $ parent, jeśli nie chcemy zmieniać naszego modelu z prymitywnego na obiekt:
Wpisanie (powiedz „22”) w tym wejściowym polu tekstowym nie powoduje utworzenia 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).
Dla wszystkich zakresów (prototypowych lub nie), Angular zawsze śledzi relacje rodzic-dziecko (tj. Hierarchia), poprzez właściwości zakresu $ parent, $$ childHead i $$ childTail. Zwykle nie pokazuję tych właściwości zakresu na diagramach.
W scenariuszach, w których elementy formularza nie są zaangażowane, innym rozwiązaniem jest zdefiniowanie funkcji w zakresie nadrzędnym w celu zmodyfikowania operacji podstawowej. Następnie upewnij się, że dziecko zawsze wywołuje tę funkcję, która będzie dostępna dla zakresu potomnego z powodu dziedziczenia prototypowego. Na przykład,
Oto przykładowe skrzypce, które wykorzystują to podejście „funkcji nadrzędnej”. (Skrzypek został napisany w ramach tej odpowiedzi: https://stackoverflow.com/a/14104318/215945 .)
Zobacz także https://stackoverflow.com/a/13782671/215945 i https://github.com/angular/angular.js/issues/1267 .
przełącznik ng
Dziedziczenie zakresu ng-switch działa tak samo jak ng-include. Jeśli więc potrzebujesz dwukierunkowego powiązania danych z operacją podstawową w zakresie nadrzędnym, użyj $ parent lub zmień model na obiekt, a następnie powiąż z właściwością tego obiektu. Pozwoli to uniknąć ukrywania / cieniowania zakresu potomnego właściwości zakresu macierzystego.
Zobacz także AngularJS, powiązać zakres skrzynki przełączników?
powtórz ng
Powtarzanie Ng działa trochę inaczej. Załóżmy, że mamy w naszym kontrolerze:
A w naszym HTML:
Dla każdego elementu / iteracji ng-repeat tworzy nowy zakres, który prototypowo dziedziczy z zakresu nadrzędnego, ale przypisuje również wartość elementu do nowej właściwości w nowym zakresie potomnym . (Nazwą nowej właściwości jest nazwa zmiennej pętli.) Oto, czym właściwie jest kod źródłowy Angulara dla ng-repeat:
Jeśli element jest prymitywny (jak w myArrayOfPrimitives), w zasadzie kopia wartości jest przypisywana do nowej właściwości zasięgu potomnego. Zmiana wartości właściwości zakresu potomnego (tj. Użycie modelu ng, a więc zakresu potomnego
num
) nie zmienia tablicy, do której odwołują się zakresy rodzicielskie. Tak więc w pierwszym powtórzeniu ng powyżej każdy zakres potomny otrzymujenum
właściwość niezależną od tablicy myArrayOfPrimitives:To powtórzenie ng nie będzie działać (tak jak tego chcesz / oczekujesz). Wpisywanie w polach tekstowych zmienia wartości w szarych polach, które są widoczne tylko w zakresach potomnych. Chcemy, aby dane wejściowe wpływały na tablicę myArrayOfPrimitives, a nie na właściwość pierwotną zakresu potomnego. Aby to osiągnąć, musimy zmienić model na tablicę obiektów.
Tak więc, jeśli element jest obiektem, odwołanie do oryginalnego obiektu (nie kopii) jest przypisywane do nowej właściwości zasięgu potomnego. Zmiana wartości tego mienia zakres dziecka (czyli używając NG-modelu, stąd
obj.num
) ma zmienić obiekt odniesienia, zakres rodzicielskich. Tak więc w drugim powtórzeniu ng powyżej mamy:(Pokolorowałem jedną linię na szaro, aby było jasne, dokąd zmierza).
Działa to zgodnie z oczekiwaniami. Wpisywanie w polach tekstowych zmienia wartości w szarych polach, które są widoczne zarówno dla zakresu potomnego, jak i macierzystego.
Zobacz także Trudność z modelem ng, powtórzeniem ng i wejściami oraz https://stackoverflow.com/a/13782671/215945
kontroler ng
Zagnieżdżanie kontrolerów za pomocą kontrolera ng powoduje normalne dziedziczenie prototypów, podobnie jak ng-include i ng-switch, więc obowiązują te same techniki. Jednak „uważa się, że dzielenie się informacjami przez dwóch kontrolerów za pomocą dziedziczenia $ scope jest uważane za złą formę” - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Należy udostępnić usługę między danymi kontrolery zamiast.
(Jeśli naprawdę chcesz udostępniać dane poprzez dziedziczenie zakresu kontrolerów, nie musisz nic robić. Zasięg 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
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 blokuje istniejącą właściwość. To nie jest dobry wybór do pisania dyrektyw, które mają być komponentami wielokrotnego użytku.scope: true
- dyrektywa tworzy nowy zakres potomny, który prototypowo dziedziczy z zakresu macierzystego. Jeśli więcej niż jedna dyrektywa (dla tego samego elementu DOM) żąda nowego zakresu, tworzony jest tylko jeden nowy zakres potomny. Ponieważ mamy „normalne” dziedziczenie prototypów, działa to podobnie do ng-include i ng-switch, więc bądź ostrożny w przypadku dwukierunkowego wiązania danych z operacjami podstawowymi zakresu nadrzędnego oraz ukrywania / cieniowania zakresu potomnego właściwości zakresu nadrzędnego.scope: { ... }
- dyrektywa tworzy nowy zakres izolacji / izolacji. Nie dziedziczy prototypowo. Jest to zwykle najlepszy wybór podczas tworzenia komponentów wielokrotnego użytku, ponieważ dyrektywa nie może przypadkowo odczytać lub zmodyfikować zakresu nadrzędnego. Jednak takie dyrektywy często wymagają dostępu do kilku właściwości zakresu nadrzędnego. Skrót obiektu służy do konfigurowania wiązania dwukierunkowego (przy użyciu „=”) lub wiązania jednokierunkowego (przy użyciu „@”) między zakresem nadrzędnym a zakresem izolowania. Dostępne są również znaki „&” do powiązania z wyrażeniami zakresu nadrzędnego. Tak więc wszystkie one tworzą właściwości zakresu lokalnego, które pochodzą z zakresu nadrzędnego. Zauważ, że atrybuty służą do konfigurowania powiązania - nie możesz po prostu odwoływać się do nazw właściwości zakresu nadrzędnego w skrócie obiektu, musisz użyć atrybutu. Np. To nie zadziała, jeśli chcesz powiązać z właściwością nadrzędnąparentProp
w izolowanym zakresie:<div my-directive>
orazscope: { localProp: '@parentProp' }
. Do określenia każdej właściwości nadrzędnej, z którą dyrektywa chce się powiązać, należy użyć atrybutu:<div my-directive the-Parent-Prop=parentProp>
iscope: { localProp: '@theParentProp' }
.Izoluj
__proto__
referencje zakresu Obiekt. Wyodrębnij zakres nadrzędny $ odwołuje się do zakresu nadrzędnego, więc chociaż jest izolowany i nie dziedziczy prototypowo z zakresu nadrzędnego, nadal jest zakresem podrzędnym.Na poniższym zdjęciu mamy
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
iscope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
załóżmy, że dyrektywa robi to w funkcji łączenia:
scope.someIsolateProp = "I'm isolated"
Aby uzyskać więcej informacji na temat zakresów izolowanych, patrz http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- dyrektywa tworzy nowy „transkoncepcyjny” zakres potomny, który prototypowo dziedziczy z zakresu macierzystego. Uwzględniony i izolowany zakres (jeśli istnieje) jest rodzeństwem - właściwość $ parent każdego zakresu odnosi się do tego samego zakresu macierzystego. Jeśli istnieje zakres transkludowany i zakres izolowany, właściwość isolate scope $$ nextSibling będzie odwoływać się do zakresu włączonego. Nie znam żadnych niuansów w zakresie objętym transkludją.Na poniższym zdjęciu załóż tę samą dyrektywę, co powyżej z tym dodatkiem:
transclude: true
To skrzypce ma
showScope()
funkcję, której można użyć do zbadania izolowanego i transkludowanego zakresu. Zobacz instrukcje w komentarzach w skrzypcach.Podsumowanie
Istnieją cztery typy zakresów:
scope: true
scope: {...}
. Ten nie jest prototypowy, ale „=”, „@” i „&” zapewniają mechanizm dostępu do właściwości zakresu nadrzędnego za pośrednictwem atrybutów.transclude: true
. Ten jest również normalnym dziedziczeniem zakresu prototypowego, ale jest również rodzeństwem dowolnego zakresu izolowanego.Dla wszystkich zakresów (prototypowych lub nie), Angular zawsze śledzi relacje rodzic-dziecko (tj. Hierarchia), poprzez właściwości $ parent i $$ childHead i $$ childTail.
Diagramy zostały wygenerowane za pomocą graphvizPliki „* .dot” znajdujące się na github . Inspiracją do wykorzystania GraphViz do diagramów była „ Uczenie się JavaScript z wykresami obiektowymi ” Tima Caswella.
źródło
__proto__
referencje zakresu Obiekt”. zamiast tego powinno być „Izoluj__proto__
odwołania zakresu obiekt Scope”. Tak więc na dwóch ostatnich obrazach pomarańczowe pola „Obiekt” powinny zamiast tego być polami „Zakres”.W żaden sposób nie chcę konkurować z odpowiedzią Marka, ale chciałem tylko podkreślić ten element, 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ść czyta wyszukiwanie łańcucha prototypów, a nie zapisuje. Więc kiedy ustawisz
Nie wyszukuje łańcucha, ale po ustawieniu
w tej operacji zapisu zachodzi subtelny odczyt, który próbuje wyszukać myThing przed zapisaniem do jego rekwizytu. Dlatego właśnie pisanie do object.properties od dziecka dostaje się do obiektów rodzica.
źródło
Chciałbym dodać przykład prototypowego dziedziczenia z javascript do odpowiedzi @Scott Driscoll. Będziemy używać klasycznego wzorca dziedziczenia z Object.create (), który jest częścią specyfikacji EcmaScript 5.
Najpierw tworzymy funkcję obiektu „Parent”
Następnie dodaj prototyp do funkcji obiektu „Nadrzędny”
Utwórz funkcję obiektu „Dziecko”
Przypisz prototyp potomny (Spraw, by prototyp potomny odziedziczył po prototypie rodzica)
Przypisz odpowiedni konstruktor prototypu „Dziecko”
Dodaj metodę „changeProps” do prototypu podrzędnego, który przepisze wartość właściwości „pierwotnej” w obiekcie podrzędnym i zmieni wartość „object.one” zarówno w obiektach podrzędnych, jak i nadrzędnych
Zainicjuj obiekty Parent (tata) i Child (syn).
Wywołaj metodę child (son) changeProps
Sprawdź wyniki.
Pierwotna pierwotna właściwość nie uległa zmianie
Zmieniono pierwotną właściwość potomną (przepisano)
Zmieniono właściwości obiektu nadrzędnego i podrzędnego object.one
Przykład działania tutaj http://jsbin.com/xexurukiso/1/edit/
Więcej informacji na temat Object.create tutaj https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
źródło