Indice dei tutorials: http://gheryd.blogspot.com/2011/06/javascript-gwt-tutorials.html
Indice
Introducendo.
Per chi è abituato alla classica programmazione ad oggetti in C o in Java, il primo impatto con javascript risulta poco familiare, e richiede un po' di elasticità sia per quanto concerne la sintassi sia per la definizione di classe.
Javascript in quanto script e non linguaggio di programmazione puro tipizzato, permette molte libertà sugli oggetti,
Negli esempi utilizzerò analogie con la programmazione ad oggetti JAVA che spero possano aiutare nella comprensione dal punto di vista pratico.
Insomma quando parlo di classe in javascript, in realtà è un qualcosa che si comporta come una classe. Ma non lo è.
Prima di iniziare a parlare di classi e oggetti, verrà introdotto il concetto di funzione attraverso una serie di esempi. Ritengo che aver ben chiaro cosa siano le funzioni sia fondamentale nella programmazione avanzata in javascript e credo possa essere propedeutico per gli argomenti affrontati nelle sezioni successive.
Function
Come gia saprete è possibile definire una funzione in questa maniera:
function show(message) {
alert(message);
}
ma le funzioni in javascript sono oggetti il cui riferimento si può assegnare ad una variabile:
var show = function(message) {
alert(message);
}
la funzione può essere così invocata:
show("hello!");
oppure:
show.call(this, "hello!");
o ancora
(show)("hello!");
in cui il termine this definisce lo scope (spiegato in seguito) della funzione, invocata al di fuori di qualsiasi oggetto si riferisce all'oggetto contenitore window.
Una funzione, in quanto oggetto, può essere passata come parametro in ingresso ad un'altra funzione:
var myLogger = function(message) {
alert(message);
}
function show(message, logger) {
logger(message);
}
show("hello", myLogger);
Possiamo definirla in maniera anonima e invocarla al volo:
(function(message){
alert(message);
})("hello");
Possiamo definire una funzione all'interno di un altra:
function show(message) {
function log() {
alert(message);
}
log();
}
show("hello");
Ad una funzione possiamo far restituire un'altro oggetto funzione:
function createLogger() {
return function(message){
alert(message);
}
}
createLogger()("hello");
var logger = createLogger();
logger("hello2");
Il riferimento dell'oggetto funzione può essere passato su più variabili:
var show = function(message) {
alert(message);
}
var show2 = show;
show2("hello");
In quanto oggetti, le funzioni possono essere create tramite new:
var show = new Function("message", "alert(message);");
show("hello");
perchè sono oggetti, la sintassi tipica di javascript ci nasconde questa loro vera natura!
Possiamo sapere i valori dei parametri passati all'invocazione della funzione:
function test() {
var s = "";
for(var i=0;i<arguments.length;i++) {
var arg = arguments[i];
s += arg+" "+typeof(arg)+"\n";
}
alert(s);
}
test("text", 2, function(){alert("hello")}, 2.5);
E infine, come vedremo nelle sezioni successive, possiamo instanziare un oggetto usando una funzione
come costruttore:
function Test() {
//code
return 5;
}
var a = new Test();
var b = Test();
Ma attenzione, la variabile a conterrà un riferimento all'instanza di un oggetto che avrà eventuali proprietà e metodi definiti secondo quanto spiegato nelle sezioni successive, mentre b conterrà il valore numerico "5".
Verificate con un alert(a) e un alert(b) ;)
In entrambi i casi la funzione Test verrà eseguita.
Inoltre anticipo che lo scope della funzione cambia, cioè il riferimento this usato all'interno si riferirà all'oggetto che stiamo instanziando a, altrimenti all'oggetto contenitore (per esempio window per b)
Singleton
Incominciamo con la creazione di un instanza al volo:
var obj = {}
oppure
var obj = new Object();
Abbiamo assegnato alla variabile obj un'instanza di un oggetto senza definire proprietà e metodi.
Certo, così non ci serve a niente. aggiungiamogli una proprietà:
obj.prop1 = "property 1";
oppure potevamo definirla direttamente dentro l'instanza con la sintassi propertyName:value (per più proprieta utilizzare la virgola come separatore)
var obj = { prop1:"property 1" };
Proviamo a visualizzarla su un alert:
alert( obj.prop1 );
Creiamo ora un metodo alla nostra istanza che visualizza sull'alert il valore prop1:
obj.alertProp1 = function() {
alert( this.prop1 );
}
o in alternativa:
var obj = {
prop1:"property 1",
alertProp1: function() {
Per eseguire il metodo appena creato:
obj.alertProp1();
C'è ancora un'altra alternativa per accedere a tutte le proprietà e metodi del nostro oggetto:
alert( obj['prop1'] );
cioè il nome della proprietà è la chiave per accedere al suo valore con la stessa sintassi di un array.
Possiamo così anche ciclare tutte le proprietà di un oggetto:
var s = "";
for(var p in obj) {
s += p + "=" + obj[p] + " (" + typeof(obj[p]) + ")\n";
}
alert(s);
Creare in questa maniera un singleton, può essere interessante se vogliamo definire, per esempio, una serie di proprietà da passare ad una funzione:
var mySetup = {
color: "yellow",
backcolor: "black",
text: "hello",
border: 1,
click: function(event) {
alert("clicked");
}
}
function createLabel(setup) {
setup = setup || {}; //se non mi viene passato il parametro ne creo uno nuovo
var e = document.createElement("div");
e.appendChild( document.createTextNode(setup.text || "" ) );
e.style.color = setup.color || "black";
e.onclick = setup.click;
// etc...
return e;
}
var label = createLabel(mySetup),
Notate che se non aggiungete metodi al vostro singleton, abbiamo l'equivalente di una hashmap o array associativo.
A proposito di array, dimenticavo che potete aggiungere alle proprietà del vostro oggetto:
var obj = {
prop1: "property 1",
alertProp1: function(){ alert(this.prop1); },
arr: [1, "2", 3 ]
}
alert( obj.arr[1] );
La proprietà arr è quindi un riferimento ad un oggetto di tipo array che ha quindi proprietà tipo length.
for(var i=0; i<obj.arr.length; i++) {
var item = obj.arr[i];
alert(item);
}
Un modo alternativo per definire un singleton, e che io personalmente preferisco è questo:
var obj = function() {
var me = {};
me.myPublicProperty = "hello";
me.myPublicMethod = function(myparam) {
//.....
}
var myPrivateMethod = function(){
//....
}
var myPrivateProperty = "world":
return me;
}();
Notate che mettendo le parentesi tonde alla fine la funzione viene subito eseguita. La funzione restituisce un'instanza di un oggetto con metodi e proprietà.
Classe
Passiamo ora a come definire una classe.
Proviamo a creare una semplice classe che conta il numero di click su un elemento:
var ClickCounter = function(id) {
var el = document.getElementById(id);
this.counter = 0;
var me = this;
el.onclick = function(){
me.counter++;
};
this.show = function() {
alert( this.counter );
}
}
In questa classe abbiamo definito una proprietà count e un metodo show.
Si rimanda alla sezione successiva per un chiarimento sullo scope (contesto) e la visibilità delle variabili.
Possiamo definire una classe anche con questa forma:
Questo ci permette un trucco per passare parametri ad una funzione che per esempio non ne prevede.
Mi spiego meglio con un esempio pratico.
Assegno un handler all'evento onclick di un elemento DOM:
var el = document.getElementById("id);
el.onclick = function(event){
document.getElementById("showContainer").innerHTML = "clicked";
}
La mia funzione verrà chiamata dal browser sul click del mio elemento el e mostrerà il messaggio all'interno di un div con id="showContainer";
Ma se usassi la stessa funzione come handler da assegnare a più elementi, potrei avere la necessita di passare altri parametri da visualizzare nel div mentre l'unico parametro che il browser mi passerà è solo l'oggetto event.
Dunque è possibile risolvere la questione creando un factory createClickHandler e sfruttando la visibilità dei parametri passati.
var createClickHandler = function(message) {
return function(event) {
document.getElementById("showContainer").innerHTML = message;
}
}
document.getElementById("id1").onclick = createClickHandler("clicked on element 1");
document.getElementById("id2").onclick = createClickHandler("clicked on element 2");
Prototype
Precedentemente per aggiungere proprietà e motodi alla nostre classi, abbiamo usato il riferimento this all'interno del nostro costruttore.
In alternativa avremmo potuto usare il metaoggetto prototype che contiene tutti i riferimenti a proprietà e metodi precedentemente definiti e consente di aggiungerne di nuovi.
var MyClass = functon(prop) {
this.prop = prop;
var privateProp = prop;
}
MyClass.prototype.showProp = function(){
alert(this.prop);
alert(privateProp);
}
Notate che nel metodo showProp possiamo accedere alla proprietà prop tramite this ma non possiamo accedere a privateProp. (Neanche con this.privateProp).
Con prototype possiamo anche sovrascrivere un metodo esistente.
Ereditarietà
Infine concludiamo il discorso prototype per ereditare proprietà e metodi da un'altra classe:
var BaseClass = function() {
this.counter = 0;
this.incr10 = function() {
this.counter +=10
}
}
var ExtClass = function() {
this.incr20 = function() {
this.counter += 20;
}
}
ExtClass.prototype = new BaseClass(); //eredita metodi e proprietà di BaseClass
Se vi sembra un po' strano passare una nuova istanza piuttosto che il nome della classe, riguardate la sezione approfondimenti. Almeno intuitivamente dovrebbe esser chiaro che non ha senso passare solo ExtClass che è un riferimento ad un oggetto di tipo function. Significherebbe ereditare proprietà e metodi di un oggetto Function.
Se al posto della classe BaseExt avessimo definito un singleton:
var baseObj = {
count: 0,
incr10: function() { this.count +=10; }
}
avremmo potuto scrivere:
ExtClass.prototype = baseObj;
Se vogliamo utilizzare il costruttore di ExtClass al posto di quello di BaseClass.
ExtClass.prototype.constructor = ExtClass;
In questo caso ha senso passare come costruttore il riferimento ad una instanza di una funzione.
Estendere una classe
Abbiamo gia visto con "prototype" come far ereditare metodi e proprietà di un'altra classe.
E' possibile farlo anche in questo modo:
var Class1 = function() {
this.method1 = function() { alert("method class 1") }
this.prop1 = "p1";
}
var Class2 = function() {
Class1.call(this); //extends class1
this.method2 = function () {
//....
}
}
var obj = new Class2();
obj.method1(); //utilizziamo il metodo ereditato dal Class1.
Cenni sull'oggetto window
E' un oggetto che viene instanziato automaticamente dal browser quando viene caricata una nuova pagina.
E' l'oggetto contenitore di tutti i nostri oggetti, infatti se create una nuova proprietà di window:
window.myprop = "property";
Questa sarà visibile in ogni sottofunzione e oggetto da noi creato.
L'oggetto window ci consente di accedere al DOM (document object model) per poter interagire con la nostra pagina via javascript:
window.document.getElementById("id");
ma possiamo accedervi direttamente senza utilizzare il riferimento window:
document.getElementById("id");
perchè esiste una variabile implicita
var document = window.document;
Anche la funzione alert è un metodo di window
window.alert("hello");
con il riferimento diretto implicito:
var alert = window.alert;
Ci sono tanti altri oggetti importanti che sono proprietà di window come per esempio history e location.
Indice
- Indroducendo
- Function
- Singleton
- Classe
- Visibilità e scope delle variabili
- Variabili static
- Prototype
- Ereditarietà
- Estendere una classe
- Cenni sull'oggetto window
- Approfondimento
Introducendo.
Per chi è abituato alla classica programmazione ad oggetti in C o in Java, il primo impatto con javascript risulta poco familiare, e richiede un po' di elasticità sia per quanto concerne la sintassi sia per la definizione di classe.
Javascript in quanto script e non linguaggio di programmazione puro tipizzato, permette molte libertà sugli oggetti,
Negli esempi utilizzerò analogie con la programmazione ad oggetti JAVA che spero possano aiutare nella comprensione dal punto di vista pratico.
Insomma quando parlo di classe in javascript, in realtà è un qualcosa che si comporta come una classe. Ma non lo è.
Prima di iniziare a parlare di classi e oggetti, verrà introdotto il concetto di funzione attraverso una serie di esempi. Ritengo che aver ben chiaro cosa siano le funzioni sia fondamentale nella programmazione avanzata in javascript e credo possa essere propedeutico per gli argomenti affrontati nelle sezioni successive.
Function
Come gia saprete è possibile definire una funzione in questa maniera:
function show(message) {
alert(message);
}
ma le funzioni in javascript sono oggetti il cui riferimento si può assegnare ad una variabile:
var show = function(message) {
alert(message);
}
la funzione può essere così invocata:
show("hello!");
oppure:
show.call(this, "hello!");
o ancora
(show)("hello!");
in cui il termine this definisce lo scope (spiegato in seguito) della funzione, invocata al di fuori di qualsiasi oggetto si riferisce all'oggetto contenitore window.
Una funzione, in quanto oggetto, può essere passata come parametro in ingresso ad un'altra funzione:
var myLogger = function(message) {
alert(message);
}
function show(message, logger) {
logger(message);
}
show("hello", myLogger);
Possiamo definirla in maniera anonima e invocarla al volo:
(function(message){
alert(message);
})("hello");
Possiamo definire una funzione all'interno di un altra:
function show(message) {
function log() {
alert(message);
}
log();
}
show("hello");
Ad una funzione possiamo far restituire un'altro oggetto funzione:
function createLogger() {
return function(message){
alert(message);
}
}
createLogger()("hello");
var logger = createLogger();
logger("hello2");
Il riferimento dell'oggetto funzione può essere passato su più variabili:
var show = function(message) {
alert(message);
}
var show2 = show;
show2("hello");
In quanto oggetti, le funzioni possono essere create tramite new:
var show = new Function("message", "alert(message);");
show("hello");
perchè sono oggetti, la sintassi tipica di javascript ci nasconde questa loro vera natura!
Possiamo sapere i valori dei parametri passati all'invocazione della funzione:
function test() {
var s = "";
for(var i=0;i<arguments.length;i++) {
var arg = arguments[i];
s += arg+" "+typeof(arg)+"\n";
}
alert(s);
}
test("text", 2, function(){alert("hello")}, 2.5);
E infine, come vedremo nelle sezioni successive, possiamo instanziare un oggetto usando una funzione
come costruttore:
function Test() {
//code
return 5;
}
var a = new Test();
var b = Test();
Ma attenzione, la variabile a conterrà un riferimento all'instanza di un oggetto che avrà eventuali proprietà e metodi definiti secondo quanto spiegato nelle sezioni successive, mentre b conterrà il valore numerico "5".
Verificate con un alert(a) e un alert(b) ;)
In entrambi i casi la funzione Test verrà eseguita.
Inoltre anticipo che lo scope della funzione cambia, cioè il riferimento this usato all'interno si riferirà all'oggetto che stiamo instanziando a, altrimenti all'oggetto contenitore (per esempio window per b)
Singleton
Incominciamo con la creazione di un instanza al volo:
var obj = {}
oppure
var obj = new Object();
Abbiamo assegnato alla variabile obj un'instanza di un oggetto senza definire proprietà e metodi.
Certo, così non ci serve a niente. aggiungiamogli una proprietà:
obj.prop1 = "property 1";
oppure potevamo definirla direttamente dentro l'instanza con la sintassi propertyName:value (per più proprieta utilizzare la virgola come separatore)
var obj = { prop1:"property 1" };
Proviamo a visualizzarla su un alert:
alert( obj.prop1 );
Creiamo ora un metodo alla nostra istanza che visualizza sull'alert il valore prop1:
obj.alertProp1 = function() {
alert( this.prop1 );
}
o in alternativa:
var obj = {
prop1:"property 1",
alertProp1: function() {
alert( this.prop1 );
}
};Per eseguire il metodo appena creato:
obj.alertProp1();
C'è ancora un'altra alternativa per accedere a tutte le proprietà e metodi del nostro oggetto:
alert( obj['prop1'] );
cioè il nome della proprietà è la chiave per accedere al suo valore con la stessa sintassi di un array.
Possiamo così anche ciclare tutte le proprietà di un oggetto:
var s = "";
for(var p in obj) {
s += p + "=" + obj[p] + " (" + typeof(obj[p]) + ")\n";
}
alert(s);
Creare in questa maniera un singleton, può essere interessante se vogliamo definire, per esempio, una serie di proprietà da passare ad una funzione:
var mySetup = {
color: "yellow",
backcolor: "black",
text: "hello",
border: 1,
click: function(event) {
alert("clicked");
}
}
function createLabel(setup) {
setup = setup || {}; //se non mi viene passato il parametro ne creo uno nuovo
var e = document.createElement("div");
e.appendChild( document.createTextNode(setup.text || "" ) );
e.style.color = setup.color || "black";
e.onclick = setup.click;
// etc...
return e;
}
var label = createLabel(mySetup),
Notate che se non aggiungete metodi al vostro singleton, abbiamo l'equivalente di una hashmap o array associativo.
A proposito di array, dimenticavo che potete aggiungere alle proprietà del vostro oggetto:
var obj = {
prop1: "property 1",
alertProp1: function(){ alert(this.prop1); },
arr: [1, "2", 3 ]
}
alert( obj.arr[1] );
La proprietà arr è quindi un riferimento ad un oggetto di tipo array che ha quindi proprietà tipo length.
for(var i=0; i<obj.arr.length; i++) {
var item = obj.arr[i];
alert(item);
}
Un modo alternativo per definire un singleton, e che io personalmente preferisco è questo:
var obj = function() {
var me = {};
me.myPublicProperty = "hello";
me.myPublicMethod = function(myparam) {
//.....
}
var myPrivateMethod = function(){
//....
}
var myPrivateProperty = "world":
return me;
}();
Notate che mettendo le parentesi tonde alla fine la funzione viene subito eseguita. La funzione restituisce un'instanza di un oggetto con metodi e proprietà.
Classe
Passiamo ora a come definire una classe.
Proviamo a creare una semplice classe che conta il numero di click su un elemento:
var ClickCounter = function(id) {
var el = document.getElementById(id);
this.counter = 0;
var me = this;
el.onclick = function(){
me.counter++;
};
this.show = function() {
alert( this.counter );
}
}
In questa classe abbiamo definito una proprietà count e un metodo show.
Si rimanda alla sezione successiva per un chiarimento sullo scope (contesto) e la visibilità delle variabili.
Possiamo definire una classe anche con questa forma:
function ClickCounter(id) {
var el = document.getElementById(id);
this.count = 0;
var me = this;
el.onclick = function(){
me.count++;
};
this.show = function() {
alert( this.counter );
}
}
Come in altri linguaggi l'oggetto si instanzia con "new":
var cc = new ClickCounter("myButton1");
Ma allora vi chiederete a cosa si riferisce this se ci limitiamo a chiamare i costruttore senza new.
Si riferisce all'oggetto contenitore.
Proviamo a fare un test;
function ShowCiao() {
this.alert("ciao");
}
Se chiamamo la funzione:
ShowCiao();
ci viene mostrato un alert perchè this si riferisce all'oggetto window che è il contenitore di tutti gli oggetti creati nell'ambito di una pagina web.
Mentre con
new ShowCiao();
non viene mostrato l'alert perchè this si riferisce all'istanza di ShowCiao che non ha il metodo alert. Questo genererebbe un errore javascript.
Un'altro modo per creare un istanza di un oggetto è questo:
var myObjFactory = function(msg) {
var me = {};
me.myPublicProperty = msg;
me.myPublicMethod = function(myparam) {
//.....
}
var myPrivateMethod = function(){
//....
}
var myPrivateMethod = "world":
return me;
}
Praticamente è la stessa funzione che abbiamo visto nella sezione "Singleton", ma senza le parentesi tonde.
Per creare un'istanza è quindi sufficiente eseguire la funzione:
var myObj1 = myObjFactory("hello");
var myObj2 = myObjFactory("ciao");
Per esempio aggiungiamo un alert che mostra il parametro id passato al costruttore:var cc = new ClickCounter("myButton1");
Ma allora vi chiederete a cosa si riferisce this se ci limitiamo a chiamare i costruttore senza new.
Si riferisce all'oggetto contenitore.
Proviamo a fare un test;
function ShowCiao() {
this.alert("ciao");
}
Se chiamamo la funzione:
ShowCiao();
ci viene mostrato un alert perchè this si riferisce all'oggetto window che è il contenitore di tutti gli oggetti creati nell'ambito di una pagina web.
Mentre con
new ShowCiao();
non viene mostrato l'alert perchè this si riferisce all'istanza di ShowCiao che non ha il metodo alert. Questo genererebbe un errore javascript.
Un'altro modo per creare un istanza di un oggetto è questo:
var myObjFactory = function(msg) {
var me = {};
me.myPublicProperty = msg;
me.myPublicMethod = function(myparam) {
//.....
}
var myPrivateMethod = function(){
//....
}
var myPrivateMethod = "world":
return me;
}
Praticamente è la stessa funzione che abbiamo visto nella sezione "Singleton", ma senza le parentesi tonde.
Per creare un'istanza è quindi sufficiente eseguire la funzione:
var myObj1 = myObjFactory("hello");
var myObj2 = myObjFactory("ciao");
Visibilità e scope della variabili
Riferiamoci sempre all'esempio precedente, la classe ClickCounter.
All'interno del costruttore abbiamo dichiarato due variabili:
this.count = 0
e
var el = document.getElementBy(id);
La differenza è che anteponendo this alla variabile questa è accessibile all'esterno, mentre con var è visibile solo all'interno del contenitore comprese sotto classi/funzioni. Praticamente è come dichiararla private.
Inoltre abbiamo creato una terza variabile private:
var me = this;
A questo punto vi chiederete perchè nella funzione di onclick ho usato me mentre per la funzione show ho potuto utilizzare this per riferirmi all'instanza della classe ClickCounter.
Nell'assegnazione
Mentre show è una proprietà di ClickCounter, quindi this si riferisce ad una sua istanza.
Nell'assegnazione
el.onclick = function(){
me.counter++;
};
se avessimo usato this ci saremmo riferiti all'oggetto contenitore el che possiede appunto la proprietà onclick. Lo scope della funzione quando invocata dal bowser è el, non il mio oggetto ClickCounter. Quindi this all'interno della funzione handler di onclick si riferisce ad el.Mentre show è una proprietà di ClickCounter, quindi this si riferisce ad una sua istanza.
Possiamo utilizzare me all'interno della funzione di onclick perchè ogni variabile dichiarata con var a livello superiore è visibile a livello più interno.
Per esempio:
var a = 1;
alert( a ); //show "1"
function TestClass() {
var b = 2;
alert( a ); //show "1"
alert( b ); //show "2"
function innerFunc() {
var c = 3;
alert( a ); //show "1"
alert( b ); //show "2"
alert( c ); //show "3"
}
alert( c ); //show "undefined"
}
alert( b ); //show "undefined"
alert( c ); //show "undefined"
Inoltre in ogni sotto funzione sono visibili anche i parametri passati come argomento al costruttore di ClickCounter.
Per esempio:
var a = 1;
alert( a ); //show "1"
function TestClass() {
var b = 2;
alert( a ); //show "1"
alert( b ); //show "2"
function innerFunc() {
var c = 3;
alert( a ); //show "1"
alert( b ); //show "2"
alert( c ); //show "3"
}
alert( c ); //show "undefined"
}
alert( b ); //show "undefined"
alert( c ); //show "undefined"
Inoltre in ogni sotto funzione sono visibili anche i parametri passati come argomento al costruttore di ClickCounter.
function ClickCounter(id) {
var el = document.getElementById(id);
this.count = 0;
var me = this;
el.onclick = function(){
me.count++;
alert(id);
alert(id);
};
this.show = function() {
alert( this.counter );
}
}
Mi spiego meglio con un esempio pratico.
Assegno un handler all'evento onclick di un elemento DOM:
var el = document.getElementById("id);
el.onclick = function(event){
document.getElementById("showContainer").innerHTML = "clicked";
}
La mia funzione verrà chiamata dal browser sul click del mio elemento el e mostrerà il messaggio all'interno di un div con id="showContainer";
Ma se usassi la stessa funzione come handler da assegnare a più elementi, potrei avere la necessita di passare altri parametri da visualizzare nel div mentre l'unico parametro che il browser mi passerà è solo l'oggetto event.
Dunque è possibile risolvere la questione creando un factory createClickHandler e sfruttando la visibilità dei parametri passati.
var createClickHandler = function(message) {
return function(event) {
document.getElementById("showContainer").innerHTML = message;
}
}
document.getElementById("id1").onclick = createClickHandler("clicked on element 1");
document.getElementById("id2").onclick = createClickHandler("clicked on element 2");
Variabili static
Utilizzo il termine static in analogia con la programmazione JAVA intendendo variabili accessibili senza utilizzare un instanza.
var MyClass = function(prop) {
this.prop = prop;
};
Definisco così una proprietà static:
MyClass.staticProp = "static proprerty";
Attenzione però, questa proprietà non è accessibile da una instanza (per un chiarimento vedere la sezione "approfondimento" alla fine di questo tutorial):
var o = new MyClass(prop);
alert( o.staticProp ); //show undefined
Mentre è accessibile in questo modo:
alert( MyClass.staticProp );
Prototype
Precedentemente per aggiungere proprietà e motodi alla nostre classi, abbiamo usato il riferimento this all'interno del nostro costruttore.
In alternativa avremmo potuto usare il metaoggetto prototype che contiene tutti i riferimenti a proprietà e metodi precedentemente definiti e consente di aggiungerne di nuovi.
var MyClass = functon(prop) {
this.prop = prop;
var privateProp = prop;
}
MyClass.prototype.showProp = function(){
alert(this.prop);
alert(privateProp);
}
Notate che nel metodo showProp possiamo accedere alla proprietà prop tramite this ma non possiamo accedere a privateProp. (Neanche con this.privateProp).
Con prototype possiamo anche sovrascrivere un metodo esistente.
Ereditarietà
Infine concludiamo il discorso prototype per ereditare proprietà e metodi da un'altra classe:
var BaseClass = function() {
this.counter = 0;
this.incr10 = function() {
this.counter +=10
}
}
var ExtClass = function() {
this.incr20 = function() {
this.counter += 20;
}
}
ExtClass.prototype = new BaseClass(); //eredita metodi e proprietà di BaseClass
Se vi sembra un po' strano passare una nuova istanza piuttosto che il nome della classe, riguardate la sezione approfondimenti. Almeno intuitivamente dovrebbe esser chiaro che non ha senso passare solo ExtClass che è un riferimento ad un oggetto di tipo function. Significherebbe ereditare proprietà e metodi di un oggetto Function.
Se al posto della classe BaseExt avessimo definito un singleton:
var baseObj = {
count: 0,
incr10: function() { this.count +=10; }
}
avremmo potuto scrivere:
ExtClass.prototype = baseObj;
Se vogliamo utilizzare il costruttore di ExtClass al posto di quello di BaseClass.
ExtClass.prototype.constructor = ExtClass;
In questo caso ha senso passare come costruttore il riferimento ad una instanza di una funzione.
Estendere una classe
Abbiamo gia visto con "prototype" come far ereditare metodi e proprietà di un'altra classe.
E' possibile farlo anche in questo modo:
var Class1 = function() {
this.method1 = function() { alert("method class 1") }
this.prop1 = "p1";
}
var Class2 = function() {
Class1.call(this); //extends class1
this.method2 = function () {
//....
}
}
var obj = new Class2();
obj.method1(); //utilizziamo il metodo ereditato dal Class1.
Cenni sull'oggetto window
E' un oggetto che viene instanziato automaticamente dal browser quando viene caricata una nuova pagina.
E' l'oggetto contenitore di tutti i nostri oggetti, infatti se create una nuova proprietà di window:
window.myprop = "property";
Questa sarà visibile in ogni sottofunzione e oggetto da noi creato.
Per questo se creiamo una funzione:
function show(message){
this.alert(message);
}
show("hello");
this si riferisce all'oggetto window, in quanto lo scope della funzione show.
L'oggetto window ci consente di accedere al DOM (document object model) per poter interagire con la nostra pagina via javascript:
window.document.getElementById("id");
ma possiamo accedervi direttamente senza utilizzare il riferimento window:
document.getElementById("id");
perchè esiste una variabile implicita
var document = window.document;
Anche la funzione alert è un metodo di window
window.alert("hello");
con il riferimento diretto implicito:
var alert = window.alert;
Ci sono tanti altri oggetti importanti che sono proprietà di window come per esempio history e location.
Approfondimento
Il concetto di classe non è propriamente lo stesso dei linguaggi come Java, perchè non possiamo definire una classe, ma semmai un oggetto costruttore che esegue istruzioni che vanno ad arricchire di proprietà e metodi un oggetto vuoto, quello che stiamo istanziando.
Possiamo comunque testare se una certo oggetto è stato generato tramite una cerca funzione mediante l'operatore instanceof:
function Test(){
}
var t = new Test();
alert( t instanceof Test );
Dato che Object è la classe di base di ogni oggetto, verrà visualizzato true anche per :
alert( t instanceof Object );
Se avessimo usato la funzione typeof, avremmo ottenuto sempre "object":
alert( typeof(t) );
Ma per chiarire il concetto di classe in javascript, mi riferirò all'esempio della sezione variabili static.
La proprietà staticProp non è una variabile statica di una classe MyClass ma è una proprietà dell'oggetto MyClass.
infatti con l'istruzione:
alert( typeof(MyClass) );
vi verrà mostrato "function".
Se vi ricordate quanto visto nella sezione function, la variabile MyClass è un riferimento ad un istanza di function, quindi è un oggetto di tipo function.
A questo oggetto ho aggiunto una proprietà che ho chiamato staticProp .
Per questo non esiste la proprietà o.staticProp perche è la proprietà di un altro oggetto appunto.
In altre parole tramite il termine "new" viene creato un oggetto invocando l'esecuzione di un oggetto function. Ma questi due oggetti non condividono proprietà e metodi.
Quindi ricapitolando la creazione di un oggetto può avvenire in due maniere.
Tramite sintassi
var obj = { pro1:val1, prop2:val2, .... }
oppure
var obj = new ( new Function(params, body)).call(this=refNewObj,myParams);
La seconda versione ci consente di utilizzare lo stesso oggetto function per creare più oggetti con uguali proprietà e metodi tramite l'invocazione implicita del suo metodo call. Il riferimento this si riferisce all'oggetto contenitore, in questo caso all'oggetto che stiamo creando.
La sintassi
var obj = new MyObj();
potremmo interpretarla come se il termine new instanziasse un oggetto vuoto {} e come se il termine MyObj() eseguisse la funzione che arricchisce di metodi e proprietà il nostro oggetto {} il cui riferimento viene assegnato alla variabile obj.
Infatti è equivalente a:
var obj = {};
MyObj.call(obj);
nell'argomento del metodo call abbiamo passato il riferimento all'oggetto obj come contesto(scope) per il riferimento this.
Questo ci fa capire anche che una funzione può essere il costruttore/inizializzatore di diversi oggetti, basta passargli lo scope (il riferimento all'istanza) diverso:
var f = function(){ this.prop3 = "p3"; }
var obj1 = {prop1:"p1"};
f.call(obj1);
var obj2 = {prop2:"p2"}
f.call(obj2);
Nessun commento:
Posta un commento