Ereditarietà delle classi JavaScript
Impara l'ereditarietà delle classi JavaScript con extends e super: catena dei prototipi, override di metodi e proprietà, chiamata al codice padre, estensione di built-in ed ereditarietà di metodi statici.
Introduzione all'ereditarietà delle classi JavaScript
L'ereditarietà delle classi è un concetto fondamentale della programmazione orientata agli oggetti che consente a una classe di ereditare proprietà e metodi da un'altra classe. In JavaScript, l'ereditarietà delle classi viene implementata usando la parola chiave extends, che fornisce un modo per creare una classe derivata che eredita da una classe base.
Per ulteriori informazioni sulla sintassi di base, consulta JavaScript: Classi e sintassi di base.
Questo capitolo spiega come creare classi derivate, eseguire l'override di metodi e proprietà, chiamare il parent con super, estendere classi built-in come Array ed ereditare metodi statici — oltre alla catena dei prototipi che fa funzionare tutto sotto il cofano.
Come funziona l'ereditarietà: la catena dei prototipi
La parola chiave class è zucchero sintattico sull'ereditarietà prototipale di JavaScript. Quando scrivi class Circle extends Shape, il motore crea due collegamenti:
Circle.prototype.__proto__ === Shape.prototype— in modo che i metodi delle istanze vengano risolti risalendo la catena.Circle.__proto__ === Shape— in modo che anche i metodi statici siano ereditati.
Quando leggi una proprietà o chiami un metodo su un object, il motore guarda prima all'object stesso. Se non viene trovato lì, risale la catena dei prototipi — verso Circle.prototype, poi Shape.prototype, poi Object.prototype, poi null — fermandosi alla prima corrispondenza. Questo è esattamente il motivo per cui un'istanza di Circle può chiamare un metodo definito su Shape.
Object.getPrototypeOf(obj) restituisce il collegamento successivo nella catena (il modo standard e preferito per ispezionarla). L'accessor legacy obj.__proto__ punta allo stesso object. Comprendere questa catena spiega tutto ciò che segue: l'override funziona perché un prototipo più vicino oscura uno più lontano, e super funziona perché salta esplicitamente al prototipo del parent.
Creare una classe derivata
Per creare una classe che eredita da un'altra, si usa la parola chiave extends:
In questo esempio, Circle estende Shape, il che significa che eredita le proprietà e i metodi di Shape, fornendo allo stesso tempo metodi propri. Si noti che Circle non definisce un proprio costruttore. In JavaScript, le classi derivate senza un costruttore esplicito chiamano automaticamente super() con gli stessi argomenti passati al costruttore della classe derivata.
Override dei metodi
Le classi derivate possono fare l'override dei metodi delle loro classi base per fornire un comportamento specifico alla sottoclasse.
Qui, Circle fa l'override del metodo print per riflettere il suo tipo specifico.
Chiamare i metodi del parent con super
Puoi anche chiamare il metodo di una classe parent dall'interno della classe derivata usando super.methodName(). Questo è utile quando vuoi estendere il comportamento del parent piuttosto che sostituirlo completamente.
Qui, super.print() esegue la logica del parent prima di aggiungere l'output specifico della sottoclasse.
Un override realistico: calcolo dell'area
L'override brilla quando ogni sottoclasse ha bisogno di un comportamento genuinamente diverso. Qui una Shape base definisce l'interfaccia condivisa, e ogni sottoclasse fa l'override di area() con il proprio calcolo reale. Chiamare area() su un Circle si risolve in Circle.prototype.area, che oscura la versione base.
Si noti che describe() è definito solo su Shape, eppure chiama this.area() e ottiene l'implementazione della sottoclasse. Questa è la catena dei prototipi in azione: this si riferisce sempre all'istanza effettiva, quindi la ricerca del metodo parte da Circle o Rectangle.
Override delle proprietà e lettura di super.prop
super non è limitato ai metodi — puoi leggere una proprietà definita sul prototipo del parent con super.prop, il che è utile quando una sottoclasse vuole costruire su un getter del parent piuttosto che sostituirlo.
Accesso al costruttore del parent: parola chiave super
Quando una classe estende un'altra classe, la funzione costruttore della classe derivata deve chiamare il costruttore del parent usando super() prima di poter usare this. Ecco come super viene usato nei costruttori per garantire che la classe parent venga inizializzata:
Perché super() deve essere eseguito prima di this
In una classe derivata, l'object istanza non viene creato finché super() non viene eseguito — questo è il compito del costruttore del parent. Fino ad allora, this si trova in uno stato non inizializzato, quindi toccarlo (leggere, assegnare o anche restituire l'object implicitamente) genera un ReferenceError. Questa è una regola del linguaggio, non una preferenza di stile.
Una regola correlata: se una classe derivata definisce un costruttore, deve chiamare super() da qualche parte prima di terminare, altrimenti viene generato lo stesso ReferenceError. (Una classe derivata senza un costruttore esplicito va bene — JavaScript inserisce constructor(...args) { super(...args); } automaticamente.) Una volta che super() ritorna, this è completamente inizializzato e pronto per l'uso.
Ereditarietà dei metodi statici
I membri statici appartengono alla classe stessa, non alle istanze. Poiché extends collega anche Circle.__proto__ a Shape, le classi derivate ereditano i metodi statici e possono chiamarli direttamente. Per ulteriori informazioni su come dichiararli, consulta Proprietà e metodi statici JavaScript.
All'interno di un metodo statico, this si riferisce alla classe su cui è stato invocato, quindi Shape.create chiamato come Circle.create costruisce un Circle.
Ereditarietà a più livelli
Le catene possono essere più di due livelli profondi. La ricerca del metodo risale semplicemente ulteriormente la catena dei prototipi, e super si riferisce sempre al prototipo un livello sopra la classe in cui il metodo è definito.
Estensione delle classi built-in
Puoi estendere classi native come Array, Error o Map per creare versioni specializzate che mantengono tutto il comportamento built-in aggiungendo il proprio.
Il problema con Symbol.species. Metodi come map, filter e slice restituiscono una nuova collezione. Per impostazione predefinita restituiscono un'istanza della tua sottoclasse (MyArray), non un semplice Array — il che di solito va bene, ma può sorprendere il codice che si aspetta un vero Array. Puoi tornare agli array semplici facendo l'override del getter statico Symbol.species.
Per combinare comportamenti da più sorgenti (JavaScript non ha ereditarietà multipla), consulta JavaScript Mixins.
Riepilogo
Punti chiave:
- Usa
extendsper derivare una classe; collega sia la catena dei prototipi (metodi delle istanze) che la classe stessa (metodi statici) al parent. - La ricerca di metodi e proprietà percorre la catena dei prototipi, fermandosi alla prima corrispondenza — ecco perché una definizione più vicina fa l'override di una più lontana. Ispeziona la catena con
Object.getPrototypeOf(). - In un costruttore derivato, chiama
super()prima dithis. Finchésuper()non viene eseguito, l'istanza non è inizializzata e qualsiasi uso dithisgenera unReferenceError. - Accedi esplicitamente al comportamento del parent con
super.method()osuper.prop, anche quando l'hai fatto l'override. - Puoi estendere i built-in come
Array— presta solo attenzione al comportamento diSymbol.speciesquando i metodi restituiscono nuove collezioni.
Passi successivi:
- Ereditarietà prototipale JavaScript — il meccanismo su cui sono costruite le classi.
- Proprietà e metodi statici JavaScript — dichiarare ed ereditare i membri statici.
- JavaScript Mixins — riutilizzare comportamenti tra classi non correlate senza una singola catena di ereditarietà.