Conoscere javascript: Oggetti

Javascript è un linguaggio ad oggetti con eredità prototipale, non basata su classi come i linguaggi OO più comuni; se non si tiene di conto tale differenza si possono incontrare notevoli difficoltà nello sviluppo.

Classi
In Javascript non c’è il concetto di classe, gli oggetti vengono creati a partire da normali funzioni costruttori che manipolano un oggetto vuoto creato ad’hoc ed associato alla keyword this.

Le due porzioni di codice seguenti sono del tutto equivalenti.
(Si ricordi che il nome this viene risolto con scoping dinamico)

var obj = new Object();
this = {};
Object();
var obj = this;

 

Oggetti
Anche il concetto di oggetto si differenzia da quello a cui siamo abituati a pensare.
Un oggetto Javascript è un semplice Object, un dizionario, una mappa chiave valore; non sono quindi considerati i vari aspetti generalmente caratteristici degli oggetti, come visibilità delle componenti, relazione con la classe, metodi particolari, etc.

Nota: si ricordi che javascript è un linguaggio funzionale, quindi è legittimo che il valore di un associazione contenga una funzione – da qui i metodi.

Gli oggetti Javascript non hanno quindi tipo, o meglio sono tutti di tipo Object; ciò significa che creato un oggetto con una qualche funzione costruttore, non abbiamo la garanzia che rimanga lo stesso “tipo” di oggetto.

Visibilità
In generale tutti i membri di un oggetto Javascript possono essere letti, modificati o rimossi da chiunque, senza che sia applicabile alcuna politica di controllo.

Per avere più controllo su quello che succede al nostro oggetto possiamo usare un escamotage. Le variabili locali al costruttore possono simulare delle variabili private: sono accessibili dal costruttore e, grazie alla chiusura lessicale, da tutti i metodi definiti dentro di esso; mentre dall’esterno non sono in alcun modo visibili.

var Example = function(){
   var private_var = 'Private!';
   this.method = function(){ console.log(private_var); }
}
var e = new Example();
e.method(); //Output: Private
e.private_var = 'Public?';
e.method(); //Output: Private
}

 

Ereditarietà
Javascript utilizza un ereditarietà prototipale. Mediante il campo prototype si può definire un oggetto prototipo per un costruttore, così facendo è come se tutti gli oggetti fossero creati a partire da una copia del prototipo.

In pratica per risparmiare spazio ogni oggetto tiene traccia del suo prototipo mediante il campo __proto__ . Quando un campo viene acceduto in lettura e non viene trovato nell’oggetto corrente si cerca risalendo la catena di prototipi. In scrittura invece si inserisce direttamente il campo nell’oggetto corrente, il prototipo non deve essere modificato in quanto condiviso potenzialmente da più oggetti.

var A = function(){
   this.a1 = "a1";
   this.print = function(){console.log("I'm A");}
}
A.prototype = {z: "I'm anonymous proto"}

var B = function(){
   this.b1 = "b1";
   this.print = function(){console.log("I'm B");}
}
B.prototype = new A();

var b = new B();
console.log(b.a1);           //Out1: a1
b.print();                   //Out2: I'm B
b.a1 = "aaa";
console.log(b.a1);           //Out3: aaa
console.log(B.prototype.a1); //Out4: a1
console.log(b.__proto__.a1); //Out5: a1
console.log(b.z);            //Out6: I'm anonymous proto
  1. Un campo non definito nell’oggetto è però definito nel suo prototipo.
  2. Un campo del prototipo viene “sovrascritto” dall’oggetto.
  3.  Un campo risolto nel prototipo viene modificato,
  4. senza che si modifichi il prototipo puntato dal costruttore,
  5. o dall’oggetto (è lo stesso prototipo).
  6. Per trovare un campo ‘z’ occorre risalire la catena dei prototipi di due livelli.

I problemi sorgono quando ci sono cose che sono indirettamente salvate nel prototipo, per esempio le variabili “private” del paragrafo precedente. Queste sono indirettamente referenziate mediante la chiusura lessicale dei metodi che le usano e sono condivise da tutti gli oggetti che usano il prototipo.

var A = function(){
 var a = "a";

 this.edit = function(newa){ a = newa; }
 this.print = function(){console.log(a);}
}

var B = function(){
 this.print2 = function(){console.log(a)}
}
B.prototype = new A();

var b1 = new B(), b2 = new B();

b1.print(); b2.print(); //Out: a a
b1.edit('b');
b1.print(); b2.print(); //Out: b b
b1.print2();            //ReferenceError: a is not defined

Qui si vede come le variabili “private”, come qualsiasi cosa non direttamente salvata nei campi del prototipo,  siano condivise da tutte le istanze che usano il prototipo. Questo perché i meccanismi del linguaggio proteggono dalla scrittura i campi del prototipo nella loro interezza, ma ignorano cosa succede al loro interno.

Questo si risolve facendo si che ogni istanza dell’oggetto figlio utilizzi un oggetto prototipo diverso. 

var B = function(){
   A.call(this);
   ...
}

A.call(this) significa: Chiama la funzione A e lega al this riferito dentro il this riferito in questo punto. In altre parole chiama il costruttore di A sull’oggetto su cui poi costruirà anche B, la base di partenza di ogni oggetto di tipo B sarà un oggetto di tipo A diverso.

A questo punto non è nemmeno necessario definire quale sia il prototipo di B, di fatto così facendo ignoriamo il sistema a prototipi del linguaggio.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *