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.,

Qualsiasi espressione è consentita dopoextends

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 ilstopmetodo 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 chiamaresuper(...), e (!) fallo prima di usare this.

…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 a this.
  • 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

Avanzate 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,]

Informazioni avanzate

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

  1. estendere una classe: class Child extends Parent:
    • Che significa Child.prototype.__proto__ sarà Parent.prototype, in modo che i metodi ereditati.
  2. Quando si sovrascrive un costruttore:
    • Dobbiamo chiamare parent constructor come super() in Child constructor prima di usare this.,
  3. Quando si sovrascrive un altro metodo:
    • Possiamo usare super.method() in un Child metodo per chiamare Parent metodo.
  4. 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.,

Inoltre:

  • Le funzioni freccia non hanno il propriothis osuper, quindi si adattano in modo trasparente al contesto circostante.