Ereditarietà con prototipo tra oggetti in Javascript

Abbiamo visto che gli oggetti in Javacript sono equivalenti a Dizionari o Array Associativi. In questa lezione vediamo come è possibile implementare il meccanismo di ereditarietà attraverso i prototipi.

Ereditarietà, accesso e risoluzione delle proprietà

Abbiamo visto nelle lezioni precedenti che quasi tutti gli oggetti Javascript hanno un altro oggetto associato: il prototipo.

Gli oggetti che hanno un prototipo ne ereditano tutte le proprietà. Vediamo l'esempio che segue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let persona = {
    nome: "Mario",
    cognome: "Rossi"
};

let studente = Object.create(persona);
studente.facoltà = "Ingegneria";

console.log(studente.facoltà); // Output: 'Ingegneria'
console.log(studente.nome); // Output: 'Mario'

In questo esempio abbiamo creato dapprima un oggetto persona che ha due proprietà: nome e cognome. Successivamente, creiamo un oggetto studente utilizzando Object.create e passando come prototipo l'oggetto persona. Ora abbiamo due oggetti distinti con le stesse proprietà.

Infatti, l'oggetto studente avrà una copia delle proprietà del suo prototipo persona: nome e cognome. Alla riga successiva, creiamo una nuova proprietà per l'oggetto studente: facoltà. Questa proprietà sarà solo di studente e non di persona.

Quando proviamo ad accedere ad una proprietà di un oggetto in lettura, come accade nelle due righe finali, l'interprete Javascript eseguirà le seguenti operazioni:

  1. Cerca tra le proprietà dell'oggetto, se esiste, quella desiderata e, se la trova, ne restituisce il valore. Questo è ciò che accade nella riga: console.log(studente.facoltà);. facoltà è una own property di studente.
  2. Se non trova nessuna proprietà con quell'identificatore, l'interprete sale di un livello e cerca se quella proprietà esiste tra quelle del prototipo dell'oggetto. Questo è ciò che accade nella riga: console.log(studente.nome);. nome, infatti, non è una proprietà di studente, per cui l'interprete non la troverà tra le sue proprietà ma dovrà cercare tra le proprietà del suo prototipo: persona.

Questo procedimento va sotto il nome di property resolution o risoluzione delle proprietà e viene esteso a tutta la prototype chain. Infatti, ogniqualvolta richiediamo in lettura una proprietà di un oggetto, l'interprete Javascript esegue i seguenti passaggi:

  1. Cerca tra le proprietà dell'oggetto. Se esiste, restituisce il valore della proprietà. Altrimenti cerca tra le proprietà del suo prototipo.
  2. Se tra le proprietà del prototipo la proprietà cercata non esiste, l'interprete, allora, cerca tra le proprietà del prototipo del prototipo e così via.
  3. Il procedimento si arresta quando la proprietà viene trovata nella catena dei prototipi, oppure viene raggiunto un prototipo che ha come prototipo il valore null. In quest'ultimo caso l'interprete restituisce undefined.

Vediamo un esempio più complesso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// L'oggetto A ha una proprietà 'x' ed ha come prototipo Object.prototype
let A = {
    x: 1
};

// B eredita le proprietà di A e di Object.prototype
let B = Object.create(A);
// B ha una own property 'y'
B.y = 2;

// C eredita le proprietà di B, A e Object.prototype
let C = Object.create(B);
// C ha una own property 'z'
C.z = 3;

// Le proprietà x e y vengono ereditate
// rispettivamente da A e B
console.log(C.x + C.y + C.z); // Output: 6

La risoluzione delle proprietà, tuttavia, vale se accediamo alle proprietà in lettura ma non vale se accediamo alle proprietà in scrittura. Ritorniamo all'esempio di sopra e aggiungiamo le righe:

C.x = 3;
console.log(C.x + C.y + C.z); // Output: 8

Nell'assegnare la proprietà x di C l'interprete non ha sovrascritto la proprietà x del prototipo A, bensì ha creato una nuova own property di C. La proprietà x del prototipo A è ora nascosta dalla proprietà x di C.

Anche se può sembrare asimmetrico, il fatto di risolvere le proprietà solo in lettura ma non in scrittura è ciò che permette in Javascript di implementare efficacemente l'ereditarietà sovrascrivendo negli oggetti figli quelle proprietà di cui si vuole specializzare il comportamento.

Riassumendo

Ricapitolando, in questa lezione abbiamo visto come specializzare un oggetto a partire da un prototipo. In particolare abbiamo visto come un oggetto erediti le proprietà del suo prototipo. Abbiamo poi visto come l'interprete risolva le proprietà nell'accesso in lettura, risalendo la catena dei prototipi. Ciò invece non avviene in caso di accesso in scrittura.

Nella prossima lezione vedremo come cancellare le proprietà degli oggetti Javacript.