C'è chi dice non sia utile, c'è chi dice non sia argomento semplice tanto quanto java o C# ... di fatto sviluppare JS in questo modo ne comporta i soliti vantaggi della programmazione ad oggetti, soprattutto oggi che JS sta prendendo sempre più piede, attraverso una semplicità nota ed intrinseca, tipica del JavaScript stesso.
Questa pillola non è pensata per chi è a digiuno di JavaScript, bensì per chi ne conosce potenzialità e sintassi ma non l'ha ancora approfondito con gli oggetti.
Cosa si intende per classe
Una classe è una porzione di codice portabile e riutilizzabile in grado di effettuare una o più operazioni attraverso metodi (funzioni interne pubbliche o private) o di restituire informazioni attraverso i parametri (variabili interne pubbliche o private).
Una classe in JavaScript è una funzione ?
Si, è possibile affermarlo, ma non è altrettanto possibile farlo nei confronti dell'oggetto che ne istanzia una. Se una classe è di fatto un instanceof Function, un'istanza della classe non è un instanza di funzione ma esattamente un'istanza del nome di quella funzione, ovvero un'istanza di quella classe, o per comodità un oggetto "Classe".
codice:
function Test(){}; // funzione Test
var test = new Test(); // oggetto Test
// Test è istanziato come classe [grazie a new]
alert(Test instanceof Function); // true
alert(test instanceof Function); // false
alert(test instanceof Test); // true
Una funzione può quindi essere usata sia come classe che come funzione ?
codice:
// funzione Test
function Test(){
// assegnazione di un parametro interno pubblico
this.parametro = "Ciao Mondo";
// operazione di funzione
alert(this.parametro);
};
// chiamata a funzione Test
Test(); // Ciao Mondo
// istanza di un nuovo oggetto Test
var test = new Test(); // Ciao Mondo
La differenza fondamentale tra l'utilizzo della funzione Test come tale o come classe, è che nel secondo caso possiamo sfruttare in un secondo momento il parametro interno.
codice:
// richiamo il parametro della funzione
alert(Test.parametro); // undefined
// richiamo il parametro dell'oggetto di tipo Test
alert(test.parametro); // Ciao Mondo
Questo accade perchè il riferimento all'interno della classe Test, ovvero il valore this, perde il referente di se stesso non appena la funzione cessa la sua esecuzione e nel caso di una chiamata diretta, Test.parametro, questo valore di fatto è come se non esistesse, visto che la funzione Test non è stata richiamata e visto che this.parametro è solo una parte di codice presente all'interno di questa funzione.
codice:
alert(new Test().parametro); // Ciao Mondo
alert(Test().parametro); // Errore: Test has no properties
Per questo è necessario istanziare un oggetto attraverso l'uso di Test come classe e non come funzione (sempre grazie a new), per permettere al codice interno alla classe di risalire a se stesso in qualunque momento, perlomeno finchè l'oggetto istanziato ha una validità all'interno del proprio scope.
Scope ? ... e perchè non Rubamezzetto ?
Lo scope è lo spazio in cui una variabile, una funzione o in generale un oggetto, risulta essere raggiungibile o utilizzabile. Lo scope è indispensabile per capire gli oggetti JavaScript ed il JavaScript stesso. Una volta capito alla perfezione come procedere per avere o non avere un determinato scope per ogni metodo, funzione, variabile o oggetto che sia, la strada sarà tutta in discesa.
codice:
// funzione Test
function Test(){
// parametro interno pubblico
this.parametro = "Ciao Mondo";
// parametro interno privato
var parametro = "Ciao Mondo Parallelo";
// operazione di funzione
alert(parametro);
};
// istanza di un nuovo oggetto Test
var test = new Test(); // Ciao Mondo Parallelo
alert(test.parametro); // Ciao Mondo
La variabile parametro all'interno di Test non cessa comunque di esistere dopo l'assegnazione ad oggetto, poichè il suo scope persiste all'interno di quella porzione di codice.
codice:
// funzione Test
function Test(){
// parametro privato
var parametro = "Ciao Mondo Parallelo";
// parametro pubblico
this.parametro = "Ciao Mondo";
// metodo pubblico
this.leggiParametro = function() {
alert(parametro);
};
};
var test = new Test(); // ... non accade niente ...
test.leggiParametro(); // Ciao Mondo Parallelo
alert(test.parametro); // Ciao Mondo
test.leggiParametro(); // Ciao Mondo Parallelo
Ma la parola var prima del nome della variabile è indispensabile ?
La parola var è una delle migliore amiche di JavaScript ed è quella che definisce esattamente lo scope di una variabile.
codice:
var parametro = "Variabile Globale"; // una variabile globale non ha bisogno della parola var
// ma non è nemmeno sbagliato usare la parola var per dichiarare una globale
function Test(){
// parametro privato senza var
parametro = "Ciao Mondo Parallelo";
this.leggiParametro = function() {
alert(parametro);
};
};
alert(parametro); // Variabile Globale
var test = new Test();
test.leggiParametro(); // Ciao Mondo Parallelo
alert(parametro); // Ciao Mondo Parallelo
Dichiarando invece la variabile di funzione con var non si rischia di sovrascrivere una globale ne di ritrovarsi sovrascritta la variabile di funzione.
codice:
function Test(){
// parametro privato senza var
parametro = "Ciao Mondo Parallelo";
this.leggiParametro = function() {
alert(parametro);
};
};
var test = new Test();
test.leggiParametro(); // Ciao Mondo Parallelo
var parametro = "Variabile Globale";
test.leggiParametro(); // Variabile Globale
Con var possiamo quindi stare sempre tranquilli
codice:
function Test(){
var parametro = "Ciao Mondo Parallelo";
this.leggiParametro = function() {alert(parametro);};
};
var parametro = "Variabile Globale";
var test = new Test();
test.leggiParametro(); // Ciao Mondo Parallelo
alert(parametro); // Variabile Globale
test.leggiParametro(); // Ciao Mondo Parallelo
Ma se ho una variabile privata non posso risalire a quella globale ?
Una variabile globale è come se fosse un parametro della super classe window, è quindi sempre possibile raggiungere il suo valore anche usando omonimi all'interno di una classe.
codice:
function Test(){
var parametro = "Ciao Mondo Parallelo";
this.leggiParametro = function() {alert(parametro);};
this.leggiParametroGlobale = function() {alert(window.parametro);};
};
var parametro = "Variabile Globale";
var test = new Test();
test.leggiParametroGlobale(); // Variabile Globale
test.leggiParametro(); // Ciao Mondo Parallelo
Perchè usare variabili private e non globali all'interno di una classe ?
Non c'è un motivo assoluto per scegliere di usare una variabile privata piuttosto che una globale se non quello di essere certi che il valore della variabile o del valore resttuito dalla funzione privata sarà esattamente quello che ci aspettiamo all'interno dell'oggetto e che nessuno potrà quindi modificarlo. Uno dei limiti più consistenti della programmazione ad oggetti in JavaScript è infatti proprio quello di non poter garantire consistenza agli oggetti.
Per consistenza si intende la possibilità di impedire ad altri di modificare metodi o parametri pubblici dei nostri oggetti.
codice:
function Test(){
this.metodoPubblico = function() {
alert("Ciao Mondo");
};
};
var test = new Test();
test.metodoPubblico(); // Ciao Mondo
// un altra porzione di codice fa, "per sbaglio", questa operazione
test.metodoPubblico = "Ciao Mondo"; // operazione sempre lecita per un oggetto
// il metodo pubblico dell'oggetto test è stato distruto
test.metodoPubblico(); // Error: test.metodoPubblico is not a function
Qualora un metodo od una variabile dovessero essere utili solo per poter utilizzare altri metodi pubblici è consigliabile sfruttare funzioni o variabili interne, quindi private, al fine di limitare la possibilità ad altri di modificare porzioni indispensabili di codice.
Ma perchè continui a dire "altri" ? Sono io che scrivo le classi ed il codice ... !!!
Le considerazioni da fare sono almeno un paio
- in applicativi dove il codice è veramente tanto e le classi create da centinaia di linee può capitare di "dimenticarsi" di un nome di metodo o parametro già usati per altri scopi, limitare l'utilizzo di nomi al fine di avere pubblici solo quelli fondamentali per l'oggetto può essere di aiuto
- JavaScript si include nelle pagine con una facilità impressionante, Google, come la super mega libreria "Cinema Effects with only 200Kb", potrebbero in qualche modo cambiare il vostro codice e viceversa, senza usare i giusti accorgimenti potreste voi stessi distruggere il codice di altre librerie presenti che avete incluso nella pagina
Ok per le variabili, qualcosa sulle funzioni / metodi privati ?
Assegnare un parametro o un metodo pubblico è molto semplice, basta usare this davanti al nome del metodo o parametro. Allo stesso tempo all'interno di un metodo pubblico il this sarà riferito all'oggetto stesso.
codice:
function Test(){
this.confronta = function(variabile) {
alert(this === variabile);
};
};
var test = new Test();
test.confronta(test); // true
test.confronta(Test); // false
Questo permette al metodo pubblico di risalire ad altri metodi pubblici o variabili pubbliche attraverso l'uso del prefisso this, riferendosi sempre all'oggetto stesso.
codice:
function Test(){
this.mostraPippo = function() {
alert(this.pippo);
}
};
var test = new Test();
var oggetto = new Test();
oggetto.pippo = "Yuk!";
oggetto.mostraPippo(); // Yuk!
test.mostraPippo(); // undefined
Quanto detto serve per introdurre i metodi privati, o per meglio definirli, le funzioni a scope locale. Queste funzioni possono essere innestate nella classe come nel metodo pubblico e sebbene non abbiano la parola var davanti hanno le stesse identiche regole di scope delle variabili.
codice:
function Test(){
// "metodo" privato / funzione privata
function avviso(){alert("Avviso Generico")};
this.avviso = function() {
avviso();
};
this.mostraAvviso = function(messaggio) {
// funzione locale
function avviso(){alert(messaggio)};
avviso();
};
};
var test = new Test();
test.mostraAvviso("Ciao Mondo"); // Ciao Mondo
test.avviso(); // Avviso Generico
In questo "semplice" esempio c'è molto di quanto detto sullo scope, sulle funzioni innestate e sui metodi privati. E' importante notare che la funzione locale al metodo mostraAvviso nonsovrascrive quella privata definita nello scope della classe (la prima), come è importante notare che la funzione innestata al metodo mostraAvviso non ha in questo caso nemmeno bisogno di una variabile come argomento, poichè lo scope della variabile messaggio, inviata al metodo dell'oggetto, ha validità per tutto il metodo stesso ed ogni porzione di codice creata al suo interno potrà sfruttare questa variabile.
Altra nota di rilievo è che in questo caso non è possibile, una volta oscurato lo scope della prima funzione "avviso()", risalire alla stessa, poichè this.avviso, all'interno del metodo, come Test.avviso, sempre all'interno o fuori dall'oggetto, non saranno utilizzabili, proprio grazie al fatto di essere private.