43 OOP5 – this, this e ancora this

abbiamo visto nella lezione 42 l’utilizzo delle copie distinte delle proprietà di un prototipo, ecco il precedente codice:

function Persona(nome, cognome)
{    
  //stato interno
 
  this.cognome = cognome; //OK OGNI istanza avrà la sua copia di cognome
  this.nome = nome;    //OCCHIO! senza this la variabile non sarà disponibile

   //ogni istanza avrà la sua copia di etichetta
  this.etichetta = function() 
    { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`} 
}

let fantozzi = new Persona("Ugo", "Fantozzi");
let filini = new Persona("Renzo Silvio", "Filini");

//gli oggetti fantozzi e filini hanno le loro copie distinte
//delle variabili dello stato interno
writeln("1: " + fantozzi.etichetta());
writeln("2: " + filini.etichetta());

Per gli oggetti che poi verranno creati, esiste uno stato interno separato

this.cognome = cognome; //OK OGNI istanza avrà la sua copia di cognome
this.nome = nome;

Cioè ogni this.nome e ogni this.cognome si troveranno come variabili indipendenti in ciascuna delle istanze che andremo a creare. fantozzi avrà le SUE variabili per nome e cognome, e così anche filini.

Il concetto che analizzeremo è capire i multipli significati che può assumere la keyword this. Dipende da come la funzione viene chiamata:

  1. Può essere una semplice funzione a se stante
    Persona("","zzzzzz");
  2. Come costruttore di un oggetto
    let fantozzi = new Persona("Ugo", "Fantozzi");
  3. Come metodo interno richiamabile su un oggetto
    this.etichetta = function() 
      { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`}

Primo caso – funzione indipendente

Quando viene invocata come funzione isolata, il this fa riferimento all’ambiente che contiene la funzione stessa, che corrisponde all’oggetto predefinito window, ecco un esempio che lo dimostra, aggiungiamo un alert nella funzione Persona che controlla se il this è uguale a window:

alert(this===window);

essendo inserito nella funzione, questo alert verrà richiamato per 3 volte (1 quando creo Persona(“”,””); e 2 quando creo fantozzi e filini) .

function Persona(nome, cognome)
{    
  this.cognome = cognome; //OK OGNI istanza avrà la sua copia di cognome
  this.nome = nome;    

  alert(this===window);

  this.etichetta = function() 
    { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`} 
}

Persona("","zzzzzz");

let fantozzi = new Persona("Ugo", "Fantozzi");
let filini = new Persona("Renzo Silvio", "Filini");

Il primo alert restituirà TRUE mentre gli altri 2 essendo invocata la funzione come costruttore il this fa riferimento a Persona non a window e restituirà FALSE

Nel primo caso viene da chiedere dove vanno a finire nome e cognome, vengono inglobate nell’oggetto window diventando variabili globali. Infatti se chiedo una stampa:

Persona("","prova-cognome");
writeln(window.cognome);

non ottengo undefine, ma prova-cognome. Per convenzione window si può omettere, quindi la stampa è

writeln(cognome);

Riassumendo: associare tramite il this delle variabili nel contesto di una funzione indipendente, equivale ad aggiungere nell’ambiente globale tali variabili.

Secondo caso – funzione costruttore

Ora togliendo l’invocazione Persona(“”,”prova-cognome”); e spostando la stampa sotto fantozzi e filini

let fantozzi = new Persona("Ugo", "Fantozzi");
let filini = new Persona("Renzo Silvio", "Filini");
writeln(cognome);

Ottengo l’errore di variabile non definita o non trovata, a dimostrazione del fatto che quando la funzione viene invocata come costruttore di oggetti, il this non fa bind (legame) all’ambiente globale, ma cognome e nome vengono copiate separatamente dentro le istanze create (fantozzi e filini)

writeln(fantozzi.cognome + " " + filini.cognome);

Quando la funzione viene chiamata in qualità di costruttore, viene creato dal motore js un oggetto vuoto

{}

in questo caso il this non punta più a window ma punta a questo oggetto vuoto creato da js, con

this.cognome = cognome;
this.nome = nome;

stiamo associando le 2 variabili a questo oggetto che poi le passerà alle istanze create col new, per cui fantozzi e filini si ritroveranno il proprio nome e cognome .

Terzo caso – invocazione di metodo

Quando il this viene chiamato nel contesto di una function, come nel nostro esempio:

this.etichetta = function() 
  { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`}

Il this fa riferimento all’oggetto stesso sul quale è stato chiamato, quindi richiamando la stampa del metodo Etichetta sui 2 oggetti creati, il this farà riferimento a fantozzi e filini

writeln("1: " + fantozzi.etichetta());
writeln("2: " + filini.etichetta());

Stamperà

1: Egr. Sig. Lup. Mann. Ugo Fantozzi
2: Egr. Sig. Lup. Mann. Renzo Silvio Filini

Ora dovrebbe essere più chiaro il significato che può assumere il this nei vari contesti.

Ecco perchè se aggiungiamo una variabile con il let nella function Persona

let local_var=999;

sarebbe un errore nel contesto del costruttore, senza this davanti la variabile non sarà disponibile nelle istanze degli oggetti creati con questa funzione e questa local_var sarà disponibile solo internamente alla funzione Persona e non richiamabile da nessun’altra parte.

Il quesito da porsi è come rappresentare in js una variabile unica disponibile all’esterno (quello che in altri linguaggi si chiama variabile static). Ci viene sempre in aiuto il meccanismo citato prima di creazione dell’oggetto vuoto, quando questo viene creato, viene anche associato ad esso un prototipo.
Il costruttore, infatti, si ritroverà con una property di default chiamata prototype richiamabile all’esterno

Persona.prototype.s = "ciao";
Persona.prototype.ETA_MIN=18;

prototype punta proprio a quell’oggetto che poi farà da prototipo per le istanze create

alert(fantozzi.s); alert(filini.s);
alert(fantozzi.ETA_MIN); alert(filini.ETA_MIN);

ecco che i nostri 2 oggetti avranno la loro proprietà in copia unica o static property, indipendentemente da numero di oggetti che andremo a creare, eseguendo otterremo lo stesso valore di stampa (2 volte il ciao e 2 volte 18).

Stessa cosa per il metodo etichetta, vogliamo mantenerlo unico:

Persona.prototype.etichettaCondivisa = function() 
    { return `Egr. Sig. Lup. Mann. ${this.nome} ${this.cognome}`}

verifichiamo il confronto tra etichetta della funzione costruttore quella non condivisa

writeln(`3: ${fantozzi.etichetta===filini.etichetta}`);

otterremo FALSE, in quanto 2 copie distinte del metodo etichetta, mentre il confronto su etichettaCondivisa restituirà TRUE

writeln(`4: ${fantozzi.etichettaCondivisa===filini.etichettaCondivisa}`);

attenzione quindi che se cambiamo il valore di una variabile statica con getPrototypeOf, tale valore cambierà per tutte le istanze

Object.getPrototypeOf(fantozzi).s = "amor";
fantozzi.s = "mare";
writeln(filini.s);

La variabile fantozzi.s verrà duplicata e maschererà quella del prototipo, infatti fantozzi.s darà mare, mentre filini.s darà amor, commentando la riga del Object.getPrototypeOf(fantozzi).s = “amor”; filini.s vale ciao .