Prototype ed ereditarietà

Prototype

In Javascript esiste un oggetto speciale, l' Object.prototype  che è il genitore di tutti gli oggetti che creiamo. Si può dire che tutti gli oggetti (di base) ereditano automaticamente le sue proprietà e sono figli (o comunque discendenti) di esso. Se richiamiamo una proprietà di un oggetto, infatti, Javascript cercherà prima tra le proprietà del nostro oggetto e, se non la trova, la cercherà in quella di Object.prototype . Vediamo un esempio:

Come vedete, abbiamo chiamato il metodo toString()  senza averlo prima dichiarato: l’abbiamo ereditato appunto dall’oggetto prototype. Questa è una prima introduzione al concetto di ereditarietà degli oggetti,  cioè alla loro capacità di essere creati sulla base di un “tipo di oggetto”.

Il metodo costruttore

Abbiamo quindi appreso che tutti gli oggetti hanno un prototype di partenza dal quale discendono. Possiamo costruirne uno noi? Ovviamente sì, molto spesso è utile costruire un oggetto come “stampo” per crearne altri, cioè definire un “tipo di oggetto”. In javascript, questo si fa con un metodo costruttore:

In questo esempio, abbiamo creato una funzione che opera da costruttore per il tipo di oggetto  Automobile con alcune caratteristiche “standard” appunto di un’autovettura. Si tratta di una semplice funzione che tramite la keyword new , ci permette di creare oggetti del “tipo”  Automobile : in questo caso gli oggetti  Panda e Ferrari si dice siano istanze dell’oggetto Automobile. Essi sono isolati l’uno dall’altro: se decidiamo di chiamare il metodo accendiTergicristalli()  dell’oggetto Panda non lo faremo anche per Ferrari .

Ogni qualvolta creiamo un oggetto, otterremo automaticamente la proprietà prototype , che contiene un collegamento al prototipo dal quale l’oggetto discende. Nel caso Ferrari.prototype  conterrà un collegamento all’oggetto Automobile .

Come avrete notato, abbiamo usato, all’interno del metodo costruttore Automobile , la keyword  this . Il suo valore, all’interno di un oggetto, fa riferimento all’oggetto stesso, com’è facile intuire. Se invece lo posizioniamo all’interno di un metodo, fa riferimento all’oggetto che lo contiene.

Aggiungere metodi e proprietà dinamicamente

A volte è necessario aggiungere (o rimuovere) dinamicamente metodi o proprietà ad un oggetto oppure ad un prototype. Partiamo con un esempio, aggiungiamo la sesta marcia all’oggetto Ferrari , perché quello Panda .. non ne avrà bisogno.

In modo molto semplice possiamo aggiungere una proprietà ad un oggetto:

Tutto questo non vale però per i prototype. Per aggiungere metodi o proprietà ad un prototype, cioè nel nostro caso aggiungerlo al costruttore Automobile , è consigliabile modificare direttamente il metodo costruttore:

L’alternativa è quella di usare la proprietà prototype . Questa proprietà, come accennato in precedenza, è generica perché ogni oggetto la possiede automaticamente. Proviamo ad usarla per aggiungere al volo un metodo o una proprietà ad un prototype già definito in precedenza:

Stiamo, in sostanza, dicendo: aggiungi un metodo e una proprietà al costruttore Automobile . In questo modo tutti gli oggetti che sono istanze di  (cioè creati a partire da) Automobile otterranno queste nuove proprietà o nuovi metodi in modo automatico.

Ereditarietà

Abbiamo visto, tramite il metodo costruttore, come creare un nostro prototype che funga da genitore per futuri oggetti basati su di esso. Fin’ora abbiamo dato per scontato che Object.prototype  fosse in genitore degli oggetti e prototype creati negli esempi. Sfruttando il concetto di ereditarietà possiamo spingerci oltre, definendo un prototype basato su un altro prototype. Questo pattern è una delle peculiarità della programmazione OOP e rende la progettazione molto flessibile. In javascript esistono varie tecniche per farlo, noi useremo una delle più diffuse:

Abbiamo definito due classi costruttrici di prototype, Automobile e AutomobileSpider perché vogliamo istanziare oggetti diversi per rappresentare autovetture normali o spider a seconda del caso. Quando creiamo un oggetto basato su AutomobileSpider , il suo costruttore chiama automaticamente il costruttore di   Automobile  con la funzione .call() . La seconda operazione è associare il prototype di AutomobileSpider a quello di Automobile in modo da assicurarci che tutti i metodi della prima siano accessibili nella seconda. A questo punto possiamo aggiungere al prototype AutomobileSpider un nuovo metodo chiamato apriCapote() :

Come vedete, dato un tipo di oggetto Automobile , ne abbiamo creato un secondo e a quest’ultimo abbiamo aggiunto delle funzionalità. Il primo, resterà ovviamente usabile.