42 OOP4 -Prototype
Ampliamo il discorso sui prototype fatto la volta scorsa, stavamo lavorando su un oggetto che modellava una “classe” persona, andiamo a svilupparlo maggiormente :
let persona = { nome: "", cognome:"", Etichetta: function() { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`} };
all’oggetto persona che per noi ha funzione di classe, ho aggiunto qualche particolare, il cognome, oltre al nome e la funzione Etichetta che restituisce semplicemente una stringa. Molto più interessante da notare è l’uso delle stringhe template che sono quelle che contengono al loro interno delle espressioni, bene delimitate dal dollaro graffa ${ }, che saranno prima valutate, e poi il valore sarà sostituito nel loro segnaposto
${this.nome}
Le condizioni per utilizzare le espressioni template, oltre all’uso del $ e delle graffe che delimitano l’espressione da valutare, occorre che tutto il valore sia racchiuso dall’ accento grave ` ( win: ALT + 96 | mac: ALT + 9 ) chiamato anche backtick o accento inclinato.
Creando l’oggetto fantozzi che usa il prototipo persona, le proprietà nome, cognome ed etichette , non sono possedute dall’oggetto, ma ereditate dal prototipo.
let fantozzi = Object.create( persona );
Esiste un metodo per capire se la proprietà è di quell’oggetto, il metodo hasOwnProperty :
writeln(fantozzi.hasOwnProperty("nome"));
la risposta a questa interrogazione sarà no, cioè FALSE. Questo metodo può rivelarsi utile ricevendo un oggetto possiamo testarne le caratteristiche, investigando sulle sue proprietà.
Il prototype di fantozzi invece ha il nome come property, ecco la relativa interrogazione sul prototipo:
writeln(Object.getPrototypeOf(fantozzi).hasOwnProperty("nome"));
Ecco péché se chiediamo una stampa dl nome in fantozzi non si otterrà un udefined, perchè la catena dei prototipi andrà a cercare a ritroso nei modelli antenati, come abbiamo visto nella scorsa lezione.
writeln(fantozzi.nome) //non ancora inizializzata ma la variabile viene trovata
Vale la pena ricordare che per assegnare un valore alle proprietà ereditate da fantozzi, BISOGNA specificare tutto il percorso a ritroso
Object.getPrototypeOf(fantozzi).nome = "Ugo"; Object.getPrototypeOf(fantozzi).cognome = "Fantozzi";
La tentazione a cui resistere è di assegnare la proprietà direttamente sull’oggetto
fantozzi.nome = "e io chi sono??";
In questo modo si aggiungerebbe una NUOVA proprietà nome che consisterebbe insieme a quella ereditata con valori differenti. La richiesta di stampa si fermerebbe subito e potremmo non ottenere il risultato voluto o di difficile interpretazione
writeln(fantozzi.nome); //e io chi sono??
ovviamente posso sempre accedere alla proprietà del modello specificandone il percorso intero
writeln(Object.getPrototypeOf(fantozzi).nome); //Ugo
in js è possibile anche eliminare col metodo delete delle proprietà, solo in locale e non. nei modelli ereditati
delete fantozzi.nome;
dopo l’eliminazione la stampa ci restituirà di nuovo ugo
writeln(fantozzi.nome); //Ugo
anche i metodi possono essere influenzati dallo shadowing, nel prototipo ho il metodo Etichetta che vado a stampare:
writeln( "1: " + fantozzi.Etichetta() );
ora vado ad aggiungere nell’oggetto locale fantozzi
fantozzi.Etichetta = function() {return "il sottoposto per eccellenza";} writeln("2: " + fantozzi.Etichetta());
Il metodo locale aggiunto oscurerà quello ereditato.
In alcuni contesti, potrebbe essere utile poter avere entrambi i metodi, in questo esempio vado a richiamare il valore del metodo prototipo da quello locale
fantozzi.Etichetta = function() {return Object.getPrototypeOf(this).Etichetta() + " detto il sottoposto";} writeln("3: " + fantozzi.Etichetta());
Nel contesto il this fa riferimento all’oggetto in cui è richiamato, nel nostro caso fantozzi, infatti è l’equivalente di scrivere:
Object.getPrototypeOf(fantozzi).Etichetta()
Gli esempi visti fino ad adesso non sono il modo migliore di utilizzo dell’ereditarietà basata sui prototype, infatti se andiamo a creare l’oggetto filini, basato sul modello persone:
let filini = Object.create( persona ); Object.getPrototypeOf(filini).cognome = "Filini"; writeln( "4: " + fantozzi.cognome );
L’oggetto filini condivide la proprietà cognome con fantozzi perchè appartenente al modello persone, assegnando un valore dall’oggetto filini, e chiedendo la stampa del cognome di fantozzi ottengo filini
Ereditarietà classica
Per implementare in js l’ereditarietà tipica dei canonici linguaggi di programmazione, dobbiamo rispolverare le funzioni costruttori di oggetto. Le variabili che dentro la function vengono stanziate con il this, verranno duplicate su ogni istanza che verrà creata con il new. Entarndo nel nostro esempio, ogni oggetto creato con il new persona e che implementa una funzione con this.nome, this.cognome, avrà la propria copia “personale” di nome e cognome. Stesso discorso per i metodi, ogni istanza che implementa this.Etichetta avrà la sua copia di Etichetta
function Persona(nome, cognome) { //stato interno this.cognome = cognome; //OK OGNI istanza avrà la sua copia di cognome this.nome = nome; //ogni istanza avrà la sua copia di etichetta this.etichetta = function() { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`} }
Spesso alcuni elementi, tipo per esempio l’etichetta può far comodo averlo condiviso per tutti i metodi, per non avere codice duplicato ridondante e che occupa più risorse.
Creiamo le istanze fantozzi e filini
let fantozzi = new Persona("Ugo", "Fantozzi"); let filini = new Persona("Renzo Silvio", "Filini");
Ora fantozzi e filini avranno le loro copie delle proprietà interne alla funzione, vediamo infatti che adesso la stampa del metodo etichetta lavora correttamente
writeln("1: " + fantozzi.etichetta()); writeln("2: " + filini.etichetta());
Per verificare che anche il metodo è duplicato facciamo un confronto tra le 2 etichette
writeln(`3: ${fantozzi.etichetta===filini.etichetta}`);
a conferma che sono 2 copie separate, l’espressione restituisce false