L’ereditarietà è una caratteristica che permette ad una classe di estendere le proprietà di altre classi.
La parola chiave “extends”
Ipotizziamo di avare una classe Animal:
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
let animal = new Animal("My animal");
QUi vediamo come rappresentare l’oggetto animal e la classe Animal graficamente:
…Potremmo voler creare un’altra class Rabbit.
Poiché i conigli sono animali, la classe Rabbit dovrebbe essere basata su Animal, avendo accesso a tutti i metodi di Animal, in questo modo Rabbit può assumere tutti i comportamenti di base di un Animal.
La sintassi utilizzate per estendere un’altra classe è: class Child extends Parent.
Creiamo class Rabbit che eredita da Animal:
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
L’oggetto della classe Rabbit ha accesso sia ai metodi di Rabbit (ad esempio rabbit.hide()) che a quelli di Animal (rabbit.run()).
Internamente, extends aggiunge da Rabbit.prototype un riferimento [[Prototype]] a Animal.prototype:
Ad esempio, per trovare il metodo rabbit.run, il motore JavaScript controlla (dal basso verso l’alto in figura):
- L’oggetto
rabbit(non possiederun). - Il suo prototype, che è
Rabbit.prototype(possiedehide, ma nonrun). - Il suo prototype, che è (a causa di
extends)Animal.prototype, che possiede il metodorun.
Come ricordiamo dal capitolo Native prototypes, JavaScript stesso usa l’ereditarietà per prototipi per gli oggetti integrati. E.g. Date.prototype.[[Prototype]] è Object.prototype. Questo è il motivo per cui le date hanno accesso ai metodi generici di un oggetto.
extendUsare la parola chiave class permette di specificare non solo una classe, ma anche un’espressione dopo la parola extends.
Per esempio, una chiamata ad una funzione che genera la classe padre:
``js run function f(phrase) { return class { sayHi() { alert(phrase); } }; }
! class User extends f(“Hello”) {} /!
new User().sayHi(); // Hello
In questo codice la class User eredita dal risultato della funzione f("Hello").
Questa particolarità può tornare utile nella programmazione avanzata, quando abbiamo bisogno di generare delle classi padre a seconda di vari parametri. ``
Sovrascrivere un metodo
Proseguiamo ora e vediamo come sovrascrivere un metodo. Di base, tutti i metodi che non vengono definiti in class Rabbit vengono presi “cosi come sono” da class Animal.
Ma se specifichiamo un metodo in Rabbit, come stop() allora verrà utilizzato questo:
class Rabbit extends Animal {
stop() {
// ...questo verrà utilizzato per rabbit.stop()
// piuttosto di stop() dal padre, class Animal
}
}
…Normalmente però non vogliamo rimpiazzare completamente il metodo ereditato, ma piuttosto costruire su di esso, modificarlo leggermente o estendere le sue funzionalità. Nel nostro metodo compiamo delle azioni, ma ad un certo punto richiamiamo il metodo ereditato.
Le classi forniscono la parola chiave "super" per questo scopo.
super.method(...)per richiamare un metodo dal padre;super(...)per richiamare il costruttore del padre (valido solo all’interno del nostro costruttore).
Per esempio, facciamo sì che il nostro coniglio si nasconda automaticamente quando si ferma: