moștenirea clasei este o modalitate prin care o clasă poate extinde o altă clasă.
astfel încât să putem crea noi funcționalități pe partea de sus a existente.
„extinde” cuvinte cheie
Să spunem că avem clasa Animal
:
Iată cum ne poate reprezenta animal
obiect și Animal
clasa grafic:
…Și am dori să creăm un alt class Rabbit
.,
Ca iepurii sunt animale, Rabbit
clasa ar trebui să se bazeze pe Animal
, au acces la animale metode, astfel încât iepurii pot face ceea ce „generic” animalele pot face.
sintaxa pentru a extinde o altă clasă este: class Child extends Parent
.
Să creăm class Rabbit
care moștenește de la Animal
:
Obiect de Rabbit
clasa au acces atât la Rabbit
metode, cum ar fi rabbit.hide()
, și, de asemenea, să Animal
metode, cum ar fi rabbit.run()
.,
intern,extends
cuvinte cheie funcționează folosind mecanica prototip vechi bun. Acesta stabilește Rabbit.prototype.]
la Animal.prototype
. Deci, dacă o metodă nu este găsită în Rabbit.prototype
, JavaScript, ia din Animal.prototype
.,
De exemplu, pentru a găsi rabbit.run
metoda, motorul controale (de jos în sus pe imagine):
Cum putem aminti la capitolul Nativ prototipuri, JavaScript se folosește prototypal moștenire pentru obiecte built-in. E. g. Date.prototype.]
este Object.prototype
. De aceea, datele au acces la metode de obiecte generice.,
extends
Clasa de sintaxă permite să se precizeze nu doar o clasa, dar orice expresie după extends
.
De exemplu, un apel de funcție care generează clasa părinte:
function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello
Aici class User
moștenește de la rezultatul de f("Hello")
.,
care pot fi utile pentru modele avansate de programare atunci când folosim funcții pentru a genera clase în funcție de multe condiții și pot moșteni de la ei.
suprascrierea unei metode
acum să mergem mai departe și să suprascriem o metodă. În mod implicit, toate metodele care nu sunt specificate în class Rabbit
sunt luate direct „ca este” de class Animal
.,
Dar dacă vom specifica metoda noastră în Rabbit
, cum ar fi stop()
atunci acesta va fi folosit în loc:
class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}
de Obicei nu vrem să înlocuiască un părinte metodă, ci mai degrabă de a construi pe partea de sus a acesteia pentru a optimiza sau a extinde funcționalitatea. Facem ceva în metoda noastră, dar apelăm metoda părinte înainte/după aceasta sau în proces.
clasele oferă "super"
cuvinte cheie pentru asta.
-
super.method(...)
pentru a apela o metodă părinte., -
super(...)
pentru a apela un constructor părinte (numai în interiorul constructorului nostru).
De exemplu, să ne iepure ascundere automată atunci când s-a oprit:
Rabbit
are stop
metoda care solicită părinte super.stop()
în acest proces.
constructor imperativ
cu Constructori devine un pic complicat.
până acum, Rabbit
nu avea propriul constructor
.,
în Conformitate cu specificația, dacă o clasă extinde o altă clasă și nu are nici un constructor
, apoi următoarele „gol” constructor
este generat:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}
după Cum putem vedea, practic numește părinte constructor
trece toate argumentele. Asta se întâmplă dacă nu scriem un constructor al nostru.
acum să adăugăm un constructor personalizat la Rabbit
. Se va specifica earLength
în plus față de name
:
Hopa!, Avem o eroare. Acum nu putem crea iepuri. Ce a mers prost?
răspunsul scurt este:
- Constructorii din clasele moștenitoare trebuie să sune
super(...)
și (!) faceți-o înainte de a utilizathis
.
…dar de ce? Ce se întâmplă aici? Într-adevăr, cerința pare ciudată.desigur ,există o explicație. Să intrăm în detalii, astfel încât să înțelegeți cu adevărat ce se întâmplă.
în JavaScript, există o distincție între o funcție constructor a unei clase moștenitoare (așa-numitul „constructor derivat”) și alte funcții., Un constructor derivat are o proprietate internă specială ]:"derived"
. E o etichetă internă specială.
această etichetă afectează comportamentul său cu new
.
- atunci Când o funcție regulate este executat cu
new
, se creează un obiect gol și atribuie-l lathis
. - dar când un constructor derivat rulează, nu face acest lucru. Se așteaptă ca constructorul părinte să facă acest lucru.,
Deci, un derivat constructor trebuie să numim super
în scopul de a executa părinte (de bază) constructor, în caz contrar obiect pentru this
nu va fi creat. Și vom primi o eroare.
Pentru Rabbit
constructor pentru a lucra, trebuie să spună super()
înainte de a utiliza this
, ca aici:
Imperative clasa domenii: un complicat notă
Acest act presupune că au o anumită experiență cu clase, poate și în alte limbaje de programare.,
Acesta oferă o perspectivă mai bună în limba și explică, de asemenea, comportamentul care ar putea fi o sursă de bug-uri (dar nu foarte des).dacă vi se pare greu de înțeles, continuați, continuați să citiți, apoi reveniți la ea ceva timp mai târziu.
putem suprascrie nu numai metodele, ci și câmpurile de clasă.
deși, există un comportament dificil atunci când accesăm un câmp suprascris în constructorul părinte, destul de diferit de cele mai multe alte limbaje de programare.,
luați în Considerare acest exemplu:
Aici, de clasă Rabbit
extends Animal
și suprascrie name
teren cu propria sa valoare.
nu e nici propriul constructor în Rabbit
, deci Animal
constructor este numit.
ceea Ce este interesant este că în ambele cazuri: new Animal()
și new Rabbit()
, alert
în linia (*)
arată animal
.,
cu alte cuvinte, constructorul părinte folosește întotdeauna propria valoare de câmp, nu cea suprascrisă.
ce este ciudat despre asta?
dacă nu este clar încă, vă rugăm să comparați cu metode.
Aici e acelasi cod, dar în loc de this.name
domeniul numim this.showName()
metoda:
vă Rugăm să notă: acum, de ieșire este diferită.
și asta ne așteptăm în mod natural. Când constructorul părinte este apelat în clasa derivată, acesta folosește metoda suprascrisă.
…dar pentru câmpurile de clasă nu este așa. După cum sa spus, constructorul părinte utilizează întotdeauna câmpul părinte.,
De ce există diferența?ei bine, motivul este în ordinea de inițializare a câmpului. Câmpul clasă este inițializat:
- înainte de constructor pentru clasa de bază (care nu extinde nimic),
- imediat după
super()
pentru clasa derivată.
în cazul nostru, Rabbit
este clasa derivată. Nu există constructor()
în ea. Așa cum am spus anterior, este același lucru ca și cum ar exista un constructor gol cu doar super(...args)
.,
Astfel, new Rabbit()
apeluri super()
, astfel executarea părinte constructor, și (pe regula pentru clasele derivate) numai după aceea clasa sa câmpuri sunt inițializate. La data de părinte constructor executie, nu există Rabbit
clasa domenii, de aceea Animal
câmpuri sunt utilizate.
această diferență subtilă între câmpuri și metode este specifică JavaScript
Din fericire, acest comportament se dezvăluie numai dacă un câmp suprascris este utilizat în constructorul părinte., Atunci poate fi dificil de înțeles ce se întâmplă, așa că explicăm aici.
dacă devine o problemă, se poate rezolva folosind metode sau getters/setters în loc de câmpuri.
Super: internals,]
dacă citiți tutorialul pentru prima dată – această secțiune poate fi omisă.este vorba despre mecanismele interne din spatele moștenirii și super
.
să ne adâncim puțin sub capotasuper
. Vom vedea câteva lucruri interesante pe parcurs.,
Mai întâi să spunem, din tot ceea ce am învățat până acum, este imposibil ca super
să funcționeze deloc!da ,într-adevăr, să ne întrebăm, cum ar trebui să funcționeze din punct de vedere tehnic? Când se execută o metodă obiect, acesta devine obiectul curent ca this
. Dacă apelăm super.method()
atunci, motorul trebuie să obțină method
din prototipul obiectului curent. Dar cum?
sarcina poate părea simplă, dar nu este., Motorul știe obiectul curent this
, deci s-ar putea obține părinte method
ca this.__proto__.method
. Din păcate, o astfel de soluție” naivă ” nu va funcționa.
să demonstrăm problema. Fără clase, folosind obiecte simple de dragul simplității.
puteți sări peste această parte și mergeți mai jos la subsecțiunea ]
dacă nu doriți să cunoașteți detaliile. Asta nu va face rău. Sau citiți mai departe dacă sunteți interesat să înțelegeți lucrurile în profunzime.
în exemplul de mai jos, rabbit.__proto__ = animal
., Acum hai să încercăm: în rabbit.eat()
vom numi animal.eat()
, folosind this.__proto__
:
La linia (*)
ne ia eat
de la prototip (animal
) și de apel în contextul actualei obiect. Vă rugăm să rețineți că .call(this)
este important aici, pentru că un simplu this.__proto__.eat()
ar executa părinte eat
în contextul prototip, nu obiectul curent.,
și în codul de mai sus funcționează de fapt conform destinației: avem corect alert
.acum, să adăugăm încă un obiect la lanț. Vom vedea cum se strică lucrurile:
codul nu mai funcționează! Putem vedea eroarea încercând să sunăm longEar.eat()
.este posibil să nu fie atât de evident, dar dacă urmărim longEar.eat()
apel, atunci putem vedea de ce. În ambele linii (*)
și (**)
valoarea this
este obiectul curent (longEar
)., Acest lucru este esențial: toate metodele obiect obține obiectul curent ca this
, nu un prototip sau ceva.
Aici este o imagine a ceea ce se întâmplă:
problema nu poate fi rezolvată prin utilizarea this
singur.pentru a furniza soluția, JavaScript adaugă încă o proprietate internă specială pentru funcții: ]
.,
când o funcție este specificată ca metodă de clasă sau obiect, proprietatea ]
devine acel obiect.
apoi super
îl folosește pentru a rezolva prototipul părinte și metodele sale.
Să vedem cum funcționează, mai întâi cu obiecte simple:
metodele nu sunt „libere”
așa cum am cunoscut înainte, în general funcțiile sunt „libere”, nu sunt legate de obiecte în JavaScript. Deci, ele pot fi copiate între obiecte și apelate cu un alt this
.,
însăși existența ]
încalcă acest principiu, deoarece metodele își amintesc obiectele. ]
nu poate fi schimbat, deci această legătură este pentru totdeauna.
singurul loc în limba în care ]
este folosit – este super
. Deci, dacă o metodă nu utilizează super
, atunci o putem considera liberă și copiată între obiecte. Dar cu super
lucrurile pot merge prost.,
Aici e demo-ul a greșit super
rezultat după copiere:
Un apel la tree.sayHi()
arată „sunt un animal”. Categoric greșit.
motivul este simplu:
Aici e diagrama de ceea ce se întâmplă:
Metode, nu în funcție de proprietăți
diferența poate fi non-esențial pentru noi, dar e important pentru JavaScript.
în exemplul de mai jos este utilizată o sintaxă non-metodă pentru comparație., ]
proprietate nu este stabilit și moștenirea nu merge:
Sumar
- Pentru a extinde o clasa:
class Child extends Parent
:- Asta înseamnă
Child.prototype.__proto__
va fiParent.prototype
, deci metode sunt moștenite.
- Asta înseamnă
- Când imperative un constructor:
- trebuie să Facem apel părinte constructor ca
super()
înChild
constructor înainte de a utilizathis
.,
- trebuie să Facem apel părinte constructor ca
- Când imperative o altă metodă:
- putem folosi
super.method()
intr-unChild
metodă de a apelaParent
metoda.
- putem folosi
- Internals:
- metode amintesc clasa lor / obiect în intern
]
proprietate. Acesta este modul în caresuper
rezolvă metodele părinte. - deci nu este sigur să copiați o metodă cu
super
de la un obiect la altul.,
- metode amintesc clasa lor / obiect în intern
de Asemenea,
- Săgeată funcții nu au propriile lor
this
sausuper
, astfel încât acestea se potrivesc în mod transparent în jur de context.
Lasă un răspuns