Class inheritance è un modo per una classe di estendere un’altra classe.
In modo da poter creare nuove funzionalità in cima a quello esistente.
La “prolunga” parola chiave
supponiamo di avere classe Animal
:
Ecco come possiamo rappresentare il animal
oggetto e Animal
classe graficamente:
…E vorremmo creare un altro class Rabbit
.,
Poiché i conigli sono animali,Rabbit
la classe dovrebbe essere basata suAnimal
, avere accesso ai metodi animali, in modo che i conigli possano fare ciò che gli animali “generici” possono fare.
La sintassi per estendere un’altra classe è: class Child extends Parent
.
creare class Rabbit
che eredita da Animal
:
Oggetto Rabbit
classe hanno accesso sia al Rabbit
metodi, come ad esempio rabbit.hide()
e anche Animal
metodi, come ad esempio rabbit.run()
.,
Internamente,extends
la parola chiave funziona usando la buona vecchia meccanica del prototipo. Imposta Rabbit.prototype.]
aAnimal.prototype
. Quindi, se un metodo non viene trovato inRabbit.prototype
, JavaScript lo prende daAnimal.prototype
.,
Per esempio, per trovare rabbit.run
metodo, il motore di controlli (bottom-up in foto):
Come si può richiamare dal capitolo prototipi Nativi, JavaScript utilizza ereditarietà prototipale per la costruzione di oggetti. Ad esempioDate.prototype.]
èObject.prototype
. Ecco perché le date hanno accesso a metodi di oggetti generici.,
extends
La sintassi della classe consente di specificare non solo una classe, ma qualsiasi espressione dopoextends
.
Per esempio, una chiamata di funzione che genera la classe padre:
function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello
class User
eredita da il risultato di f("Hello")
.,
Questo può essere utile per i modelli di programmazione avanzati quando usiamo le funzioni per generare classi a seconda di molte condizioni e possiamo ereditare da esse.
Sovrascrivendo un metodo
Ora andiamo avanti e sovrascriviamo un metodo. Per impostazione predefinita, tutti i metodi non specificati inclass Rabbit
vengono presi direttamente “così come sono” daclass Animal
.,
Ma se si specificare il nostro metodo nel Rabbit
, come stop()
poi verrà utilizzato invece:
class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}
di Solito non vogliamo sostituire completamente un metodo di padre, ma piuttosto per costruire su di esso per modificare o estendere le sue funzionalità. Facciamo qualcosa nel nostro metodo, ma chiamiamo il metodo genitore prima/dopo o nel processo.
Le classi forniscono"super"
parola chiave per questo.
-
super.method(...)
per chiamare un metodo genitore., -
super(...)
per chiamare un costruttore genitore (solo all’interno del nostro costruttore).
Per esempio, lasciate che il nostro coniglio autohide quando fermato:
Oraha ilstop
metodo che chiama il genitoresuper.stop()
nel processo.
Sovrascrivendo il costruttore
Con i costruttori diventa un po ‘ complicato.
Fino ad ora,Rabbit
non aveva il proprioconstructor
.,
Secondo la specifica, se una classe estende un’altra classe e non ha constructor
e poi la seguente “vuota” constructor
generato:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}
Come si può vedere, fondamentalmente, si chiama il padre constructor
passando di tutti gli argomenti. Ciò accade se non scriviamo un costruttore nostro.
Ora aggiungiamo un costruttore personalizzato aRabbit
. Specificherà il earLength
oltre a name
:
Whoops!, Abbiamo un errore. Ora non possiamo creare conigli. Cosa è andato storto?
La risposta breve è:
- I costruttori nelle classi ereditanti devono chiamare
super(...)
, e (!) fallo prima di usarethis
.
…Ma perché? Che succede qui? In effetti, il requisito sembra strano.
Certo, c’è una spiegazione. Entriamo nei dettagli, così capirai davvero cosa sta succedendo.
In JavaScript, c’è una distinzione tra una funzione di costruttore di una classe ereditante (il cosiddetto “costruttore derivato”) e altre funzioni., Un costruttore derivato ha una speciale proprietà interna ]:"derived"
. E ‘ un’etichetta interna speciale.
Tale etichetta influisce sul suo comportamento con new
.
- Quando una funzione regolare viene eseguita con
new
, crea un oggetto vuoto e lo assegna athis
. - Ma quando viene eseguito un costruttore derivato, non lo fa. Si aspetta che il costruttore genitore esegua questo lavoro.,
Quindi un costruttore derivato deve chiamaresuper
per eseguire il suo costruttore genitore (base), altrimenti l’oggetto perthis
non verrà creato. E avremo un errore.
Per il Rabbit
costruttore per lavorare, ha bisogno di chiamare super()
prima di usare this
, come di seguito:
classe prevalente campi: una delicata nota
Questa nota si presuppone una certa esperienza con le classi, forse in altri linguaggi di programmazione.,
Fornisce una migliore comprensione della lingua e spiega anche il comportamento che potrebbe essere una fonte di bug (ma non molto spesso).
Se si hanno difficoltà a capire, basta andare avanti, continua a leggere, poi tornare ad esso qualche tempo dopo.
Possiamo ignorare non solo i metodi, ma anche i campi di classe.
Anche se, c’è un comportamento difficile quando accediamo a un campo sovrascritto nel costruttore genitore, molto diverso dalla maggior parte degli altri linguaggi di programmazione.,
Considera questo esempio:
Qui, classRabbit
estendeAnimal
e sovrascrivename
campo con il proprio valore.
Non c’è un proprio costruttore in Rabbit
, quindi viene chiamato Animal
costruttore.
la Cosa interessante è che in entrambi i casi: new Animal()
e new Rabbit()
alert
nella riga (*)
mostra animal
.,
In altre parole, il costruttore genitore utilizza sempre il proprio valore di campo, non quello sovrascritto.
Cosa c’è di strano?
Se non è ancora chiaro, si prega di confrontare con i metodi.
Ecco lo stesso codice, ma invece dithis.name
campo che chiamiamothis.showName()
metodo:
Nota: ora l’output è diverso.
Ed è quello che ci aspettiamo naturalmente. Quando il costruttore padre viene chiamato nella classe derivata, utilizza il metodo sovrascritto.
But Ma per i campi di classe non è così. Come detto, il costruttore genitore utilizza sempre il campo genitore.,
Perché c’è la differenza?
Bene, il motivo è nell’ordine di inizializzazione del campo. Il campo classe viene inizializzato:
- Prima del costruttore per la classe base (che non estende nulla),
- Immediatamente dopo
super()
per la classe derivata.
Nel nostro caso, Rabbit
è la classe derivata. Non c’èconstructor()
in esso. Come detto in precedenza, è come se ci fosse un costruttore vuoto con solo super(...args)
.,
Quindi, new Rabbit()
chiama super()
, eseguendo così il costruttore genitore e (secondo la regola per le classi derivate) solo dopo che i suoi campi di classe sono inizializzati. Al momento dell’esecuzione del costruttore padre, non ci sono ancora campi di classe Rabbit
, ecco perché vengono utilizzati i campi Animal
.
Questa sottile differenza tra campi e metodi è specifica per JavaScript
Fortunatamente, questo comportamento si rivela solo se viene utilizzato un campo sovrascritto nel costruttore genitore., Quindi potrebbe essere difficile capire cosa sta succedendo, quindi lo stiamo spiegando qui.
Se diventa un problema, si può risolvere il problema utilizzando metodi o getter/setter invece di campi.
Super: internals,]
Se stai leggendo il tutorial per la prima volta, questa sezione potrebbe essere saltata.
Riguarda i meccanismi interni alla base dell’ereditarietà esuper
.
Andiamo un po ‘ più in profondità sotto il cofano disuper
. Vedremo alcune cose interessanti lungo la strada.,
Prima di dire, da tutto ciò che abbiamo imparato fino ad ora, è impossibile che super
funzioni affatto!
Sì, infatti, chiediamoci, come dovrebbe funzionare tecnicamente? Quando viene eseguito un metodo object, ottiene l’oggetto corrente come this
. Se chiamiamo super.method()
quindi, il motore deve ottenere ilmethod
dal prototipo dell’oggetto corrente. Ma come?
L’attività può sembrare semplice, ma non lo è., Il motore conosce l’oggetto corrente this
, quindi potrebbe ottenere il genitore method
come this.__proto__.method
. Sfortunatamente, una soluzione così “ingenua” non funzionerà.
Dimostriamo il problema. Senza classi, usando oggetti semplici per semplicità.
Puoi saltare questa parte e andare sotto alla sottosezione ]
se non vuoi conoscere i dettagli. Non farà male. Oppure continua a leggere se sei interessato a capire le cose in modo approfondito.
Nell’esempio seguente, rabbit.__proto__ = animal
., Ora cerchiamo: nel rabbit.eat()
chiameremo animal.eat()
, con this.__proto__
:
Alla riga (*)
prendiamo eat
dal prototipo (animal
) e chiamarlo nel contesto dell’oggetto corrente. Si noti che .call(this)
è importante qui, perché un semplice this.__proto__.eat()
eseguirebbe parent eat
nel contesto del prototipo, non l’oggetto corrente.,
E nel codice sopra funziona effettivamente come previsto: abbiamo il corretto alert
.
Ora aggiungiamo un altro oggetto alla catena. Vedremo come si rompono le cose:
Il codice non funziona più! Possiamo vedere l’errore cercando di chiamare longEar.eat()
.
Potrebbe non essere così ovvio, ma se tracciamolongEar.eat()
call, allora possiamo capire perché. In entrambe le righe(*)
e(**)
il valore dithis
è l’oggetto corrente (longEar
)., Questo è essenziale: tutti i metodi oggetto ottengono l’oggetto corrente come this
, non un prototipo o qualcosa del genere.
Ecco la foto di quello che accade:
Il problema non può essere risolto utilizzando this
da sola.
]
Per fornire la soluzione, JavaScript aggiunge un’altra proprietà interna speciale per le funzioni:]
.,
Quando una funzione viene specificata come metodo di classe o oggetto, la sua proprietà ]
diventa tale oggetto.
Quindi super
lo utilizza per risolvere il prototipo padre e i suoi metodi.
Vediamo come funziona, prima con oggetti semplici:
I metodi non sono “liberi”
Come abbiamo conosciuto prima, generalmente le funzioni sono “libere”, non legate agli oggetti in JavaScript. Quindi possono essere copiati tra oggetti e chiamati con un altrothis
.,
L’esistenza stessa di ]
viola questo principio, perché i metodi ricordano i loro oggetti. ]
non può essere modificato, quindi questo legame è per sempre.
L’unico posto nella lingua in cui viene utilizzato ]
è super
. Quindi, se un metodo non usa super
, allora possiamo ancora considerarlo libero e copiare tra gli oggetti. Ma con super
le cose potrebbero andare storte.,
Ecco la demo di un risultato erratosuper
dopo la copia:
Una chiamata atree.sayHi()
mostra “Sono un animale”. Decisamente sbagliato.
Il motivo è semplice:
di seguito il diagramma di quello che accade:
Metodi, non di proprietà della funzione
La differenza può non essere essenziale per noi, ma è importante per JavaScript.
Nell’esempio seguente viene utilizzata una sintassi non metodica per il confronto., ]
proprietà non è impostata, e l’ereditarietà non funziona:
Sommario
- estendere una classe:
class Child extends Parent
:- Che significa
Child.prototype.__proto__
saràParent.prototype
, in modo che i metodi ereditati.
- Che significa
- Quando si sovrascrive un costruttore:
- Dobbiamo chiamare parent constructor come
super()
inChild
constructor prima di usarethis
.,
- Dobbiamo chiamare parent constructor come
- Quando si sovrascrive un altro metodo:
- Possiamo usare
super.method()
in unChild
metodo per chiamareParent
metodo.
- Possiamo usare
- Interni:
- I metodi ricordano la loro classe/oggetto nella proprietà interna
]
. Ecco comesuper
risolve i metodi padre. - Quindi non è sicuro copiare un metodo con
super
da un oggetto all’altro.,
- I metodi ricordano la loro classe/oggetto nella proprietà interna
Inoltre:
- Le funzioni freccia non hanno il proprio
this
osuper
, quindi si adattano in modo trasparente al contesto circostante.
Lascia un commento