Menu

Ereditarietà in JavaScript: extends, super e override dei metodi

Come funziona l'ereditarietà nelle classi JavaScript: extends, super, override dei metodi e quando l'ereditarietà è lo strumento sbagliato.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Una classe che si basa su un'altra

L'ereditarietà permette a una classe di partire dal progetto di un'altra classe ed estenderlo. Ricevi gratis ogni campo e metodo della classe padre, e puoi aggiungere o cambiare tutto ciò che ti serve:

Dog extends Animal significa: "un Dog è un Animal, più qualcosa in più". rex non ha un metodo speak proprio, ma la ricerca risale fino ad Animal, che invece ce l'ha. Questo risalire è tutto il meccanismo: è semplicemente la catena dei prototipi con una sintassi più gradevole.

super nel costruttore

Una sottoclasse con un costruttore proprio ha una regola rigida: chiama super(...) prima di toccare this. super esegue il costruttore della classe padre, che è quello che crea e inizializza davvero l'oggetto:

Se salti la riga super(name) otterrai un ReferenceError nel momento in cui provi a leggere o scrivere this. Il motore si rifiuta semplicemente di darti un this finché la classe padre non ha fatto la sua parte.

Se una sottoclasse non dichiara un costruttore proprio, JavaScript ne genera uno che passa tutti gli argomenti a super: quindi devi scriverlo solo quando aggiungi campi o fai qualche inizializzazione in più.

Override dei metodi

Una sottoclasse può ridefinire qualsiasi metodo ereditato. Vince quello più vicino nella catena:

Nessuna magia: quando chiami speak() su un Dog, il motore cerca speak sull'istanza, poi su Dog.prototype, lo trova e si ferma. Non arriva mai ad Animal.prototype.

Estendere, non sostituire: super.method()

A volte non vuoi sostituire il metodo della classe padre, ma aggiungere qualcosa. super.method(...) chiama la versione della classe padre dall'interno dell'override:

È qui che l'ereditarietà si guadagna il suo posto: la sottoclasse riutilizza la logica della classe padre invece di copiarla. Se in futuro Animal.describe cambia, Dog.describe riceve la modifica gratis.

super funziona in qualsiasi metodo, non solo nel costruttore. Si riferisce sempre alla versione della classe padre di ciò che stai chiamando.

instanceof e la catena

instanceof controlla se la catena dei prototipi di un oggetto include una certa classe. Ogni istanza di una sottoclasse è anche un'istanza delle classi padre:

Tutti e quattro valgono true. La catena è Puppy -> Dog -> Animal -> Object, e instanceof la percorre. È utile per i controlli di tipo, anche se in pratica lo userai meno di quanto pensi: la maggior parte del codice chiama semplicemente i metodi e lascia che il polimorfismo faccia il resto.

Un esempio un po' più grande

Uno schema comune: una classe base con la logica condivisa e un paio di sottoclassi che la specializzano.

Nota che describe vive su Shape e non va mai riscritto: chiama semplicemente this.area(), che a runtime si risolve nella sottoclasse giusta. Questo è il polimorfismo: stessa chiamata, comportamento diverso in base all'oggetto reale.

Ereditarietà contro composizione

L'ereditarietà è allettante perché sembra produttiva: una riga ti dà una montagna di metodi. Ma invecchia male quando le gerarchie crescono.

Regola pratica: usa extends quando la relazione è chiaramente "X è un Y" e la sottoclasse condivide davvero gran parte del comportamento della classe padre. Quando usi l'ereditarietà solo per condividere uno o due metodi helper, preferisci la composizione: dai alla classe un campo che contiene l'oggetto helper.

Gli alberi di ereditarietà profondi (Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog) sembrano ordinati nei diagrammi e diventano dolorosi nel codice: una modifica vicino alla radice si propaga in modo imprevedibile in ogni discendente. La maggior parte dei progetti sani resta su uno o due livelli e usa la composizione per il resto.

Prossimo passo: membri statici

Tutto ciò che hai visto in questa pagina vive sulle istanze: metodi che chiami con new Thing().something(). A volte vuoi metodi o dati che appartengono alla classe stessa, non a una particolare istanza. È a questo che serve static, ed è l'argomento della prossima pagina.

Domande frequenti

Come funziona l'ereditarietà in JavaScript?

Una classe può ereditare da un'altra usando extends. La sottoclasse riceve ogni metodo e campo della classe padre, e può aggiungerne di propri o sovrascrivere quelli esistenti. Dietro le quinte, JavaScript collega il prototipo della sottoclasse a quello della classe padre, così la ricerca dei metodi risale in automatico.

Cosa fa super in JavaScript?

super(...) chiama il costruttore della classe padre: devi chiamarlo prima di usare this nel costruttore di una sottoclasse. super.method(...) chiama la versione del metodo definita nella classe padre, ed è così che estendi un comportamento invece di sostituirlo del tutto.

Meglio ereditarietà o composizione in JavaScript?

Usa l'ereditarietà quando c'è una vera relazione 'è un' e la sottoclasse condivide gran parte del comportamento della classe padre. Scegli la composizione, cioè oggetti che contengono altri oggetti, quando vuoi solo condividere delle funzionalità. Le gerarchie di classi profonde invecchiano male; la maggior parte dei progetti reali resta su uno o due livelli.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA