Nella di questo tutorial JavaScript abbiamo visto i costrutti principali del linguaggio. In questo post vedremo varie caratteristiche del linguaggio JavaScript arrivando a parlare di JavaScript object oriented. Alla fine di questa mini guida al linguaggio JavaScript vedremo la soluzione al mini quiz visto all’inizio della prima parte, ve lo ricordate? Sapete già la soluzione?
Scope di una function
Una function può ovviamente essere definita all’interno di un oggetto: in tal caso si può accedere ai campi dell’oggetto che la contiene tramite la variabile this
:
var persona = { dataDiNascita: new Date(1970, 1, 1), getEta: function() { var diff = new Date() - this.dataDiNascita; var diffInAnni = diff / 1000 / 60 / 60 / 24 / 365; return Math.floor(diffInAnni); } };
Un concetto importante è quello di callback: una function può essere passata come parametro di un metodo. Ad esempio i metodi dei vari framework che effettuano una chiamata ajax prendono solitamente fra i parametri anche una function da eseguire dopo la risposta del server. Vediamo un esempio semplificato:
function eseguiChiamata(parametri, success, failure) { //esegue la chiamata al server var risposta = chiamaServer(parametri); //se viene ritornato qualcosa viene invocata la function success if (risposta) { success(risposta); } else { //altrimenti viene invocata la function failure failure(risposta); } }
Ma cosa succede se una function viene invocata direttamente senza passare dall’oggetto di appartenenza, quindi con metodo(parametri)
e non con oggetto.metodo(parametro)
? In questi casi this
contiene il riferimento all’oggetto globale window
e non quello che ci aspettiamo. Per capire meglio questo concetto vediamo un esempio di una function molto utile che molti framework definiscono:
function each(array, fn) { if (array != null) { //se non passo un array creo un array al volo //con un unico elemento if(typeof array.length == "undefined" || typeof array == "string") { array = [array]; } //itero sull'array e per ogni oggetto //richiamo la function passata for(var j = 0, len = array.length; j < len; j++) { fn(array[j], j, array); } } }
Questa function prende come parametro un array e una function da chiamare su ogni elemento, se il primo parametro non è un array viene creato al volo un array di un solo elemento. La function passata come parametro viene richiamata con tre parametri: l’i-esimo elemento, l’indice i e l’array completo. Usiamo la function each
per calcolare la somma degli elementi di un array di numeri: per fare ciò creiamo un oggetto sommatore
che ha al suo interno il totale e un metodo somma
che viene passato come parametro alla function each
.
var a = [1,2,3]; var sommatore = { totale: 0, somma: function(v) { this.totale += v; } }; each(a, sommatore.somma); sommatore.totale == 0
In questo caso sommatore.totale
non contiene il valore aspettato, cerchiamo di capire perchè. Tutti le function (essendo oggetti) hanno due importanti function utilizzabili: apply
e call
. Tutte e due richiamano la function impostando lo scope: in pratica lo scope corrisponde all’oggetto che all’interno della function può essere richiamato con la variabile this
. La differenza fra i due metodi è che apply
prende come parametri lo scope e un array di parametri da passare mentre call
prende lo scope e i parametri da passare (non come array). Modifichiamo il nostro metodo each
per passare lo scope:
function each(array, fn, scope) { if (array != null) { if(typeof array.length == "undefined" || typeof array == "string") { array = [array]; } for(var j = 0, len = array.length; j < len; j++) { //lo scope è opzionale, se non viene passato //viene usato l'elemento stesso come scope fn.call(scope || array[j], array[j], j, array); } } }
Passando anche lo scope al metodo each
si ottiene il risultato aspettato:
each(a, sommatore.somma, sommatore); sommatore.totale == 6
Classi e istanze
Fino ad adesso abbiamo visto solo oggetti definiti al volo, ma in JavaScript è possibile creare anche classi e istanze di classi in stile Java. Per fare ciò è fondamentale capire il concetto di costruttore. Un costruttore è una normale function JavaScript (di solito definita con il nome in upper camel case) in cui viene eseguita la costruzione di un oggetto.
function Persona(nome, cognome, dataDiNascita) { //inizializzo i campi this.nome = nome; this.cognome = cognome; this.dataDiNascita = dataDiNascita; //aggiungo alcune function this.getEta = function() { var diff = new Date() - this.dataDiNascita; var diffInAnni = diff / 1000 / 60 / 60 / 24 / 365; return Math.floor(diffInAnni); }; }
In pratica Persona
può essere considerata una classe e quindi può essere istanziata con una new
: eseguendo la new
di un costruttore viene ritornato l’oggetto corrispondente al costruttore stesso a meno che non ci sia un return esplicito.
var p = new Persona('Mario', 'Rossi', new Date(1970, 1, 1)); p.getEta() == 41
In JavaScript è possibile aggiungere metodi a una classe anche attraverso l’oggetto prototype
: in pratica quando si chiede il valore di un campo su un oggetto (quindi anche quando si cerca di invocare un metodo su un oggetto) se tale campo non viene trovato viene cercato nell’oggetto prototype
. Quindi l’esempio visto sopra può essere riscritto aggiungendo la function getEta
al prototype
e non all’oggetto stesso (facendo così si ha anche un risparmio di memoria in quanto la function è creata una volta sola e non una volta per ogni istanza):
function Persona(nome, cognome, dataDiNascita) { //inizializzo i campi this.nome = nome; this.cognome = cognome; this.dataDiNascita = dataDiNascita; } Persona.prototype.getEta = function() { var diff = new Date() - this.dataDiNascita; var diffInAnni = diff / 1000 / 60 / 60 / 24 / 365; return Math.floor(diffInAnni); };
Il campo prototype
può essere usato anche per creare gerarchie di classi: per esempio possiamo creare la classe Studente
che estende la classe Persona
aggiungendo una nuova function getAnniDiStudio
:
//costruttore della classe derivata function Studente(nome, cognome, dataDiNascita) { Persona.call(this, nome, cognome, dataDiNascita); } //imposto il prototype a una istanza di Persona Studente.prototype = new Persona(); //aggiungo un nuovo metodo al prototype di Studente //in cui richiamo una function della classe base Studente.prototype.getAnniDiStudio = function() { return this.getEta() - 6; }
Javascript è un linguaggio dinamico, attraverso il prototype
permette addirittura (anche se molti sconsigliano questa pratica) di aggiungere metodi a classi standard. Vediamo un esempio in cui si aggiunge una function startsWith
al prototype
della classe String
:
var s = 'abcd'; String.prototype.startsWith= function(s){ return this.indexOf(s) == 0; }; s.startsWith('ab')
Possiamo notare che la function che abbiamo aggiunto può essere invocata su tutte le istanze di String
, anche quelle create prima della definizione.
Visibilità dei campi di un oggetto
In tutti gli esempi visti fino ad ora tutti i campi degli oggetti sono pubblici, in JavaScript non esiste una esplicita definizione di visibilità di un campo (non è possibile definire un campo private
o protected
come in Java). Vediamo come è possibile simulare un campo privato: per esempio rendiamo la data di nascita della classe Persona
privata:
function Persona(nome, cognome, dataDiNascita) { //inizializzo i campi this.nome = nome; this.cognome = cognome; this.getEta = function() { var diff = new Date() - dataDiNascita; var diffInAnni = diff / 1000 / 60 / 60 / 24 / 365; return Math.floor(diffInAnni); }; }
Scrivendo così non abbiamo aggiunto un campo dataDiNascita
al this
, ma usiamo direttamente il valore passato come parametro all’interno del metodo getEta
. Il metodo getEta
è una closure, ha accesso non solo ai campi pubblici dell’oggetto a cui appartiene, ma anche ai parametri della function in cui viene definito e alle variabili locali definite all’interno della function. Il metodo getEta
non è nè pubblico nè privato, viene definito come privilegiato, può accedere sia ai campi pubblici che a quelli privati. Un metodo pubblico può invece accedere solo ai campi pubblici. Un esempio un po’ ingarbugliato in cui sono usati tutti i tipi di metodi visti è il seguente:
function Costruttore(parametro) { var variabilePrivata = 5; this.variabilePubblica = parametro; function functionPrivata() { return variabilePrivata + parametro; } this.functionPrivilegiata = function(par) { return parametro + variabilePrivata + this.variabilePubblica + functionPrivata() + par + this.functionPubblica1(1); } } Costruttore.prototype.functionPubblica1 = function(par) { return par + this.variabilePubblica; } Costruttore.prototype.functionPubblica2 = function() { return this.variabilePubblica + this.functionPrivilegiata(400); } var c = new Costruttore(10); c.variabilePubblica == 10 c.functionPubblica1(100) == 110 c.functionPrivilegiata(200) == 10 + 5 + 10 + (5 + 10) + 200 + (1 + 10) c.functionPubblica2() == 10 + 451
I namespace JavaScript
Come già detto memorizzare variabili e function a livello globale dell’oggetto window
è sconsigliato. Questo perchè se si utilizzano librerie esterne si può incorrere in problemi di collisioni di nomi che portano a bug difficili da trovare. Per evitare ciò si usa il concetto di namespace che risulta molto simile a un package Java. Un namespace non è altro che un oggetto JavaScript in cui vengono aggiunte variabili, function o ulteriori namespace. Un esempio è il seguente:
var it = {}; it.cosenonjaviste = {}; //aggiungo una classe al namespace it.cosenonjaviste.OggettoBase = function() { }; //aggiungo un'altra classe facendola derivare dalla prima it.cosenonjaviste.OggettoDerivato = function() { }; it.cosenonjaviste.OggettoDerivato.prototype = new it.cosenonjaviste.OggettoBase(); //creo un sotto namespace it.cosenonjaviste.util = {}; //aggiungo una function statica it.cosenonjaviste.util.functionStatica = function(a, b) { var obj = new it.cosenonjaviste.OggettoDerivato(); };
Qualche esempio complesso
Vediamo adesso alcuni modi di usare javascript che a prima vista sembrano incomprensibili. Per esempio alcune volte viene definita e eseguita subito una function anonima per inizializzare dati senza “sporcare” l’oggetto globale window
:
(function() { var s1 = 10; var s2 = 20; oggettoGlobale.valore = s1 + s2; })();
Una cosa simile può essere usata anche per creare una function come risultato di una function. Vediamo come esempio un oggetto che contiene una function creaXmlHttpRequest
creata all’interno di una function anonima eseguita subito. In pratica quando viene creato l’oggetto viene subito invocata la function anonima e il risultato (dipendente dal browser) viene memorizzato nel campo creaXmlHttpRequest
.
var oggetto = { creaXmlHttpRequest: (function() { // codice per Mozilla if (window.XMLHttpRequest) { return function(url) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange=xmlhttpChange; xmlhttp.open("GET",url,true); xmlhttp.send(null); } } // codice per Internet Explore else if (window.ActiveXObject) { return function(url) { var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); if (xmlhttp) { xmlhttp.onreadystatechange=xmlhttpChange; xmlhttp.open("GET", url, true); xmlhttp.send(); } } } })() };
Un altro esempio un po’ particolare è il seguente, in pratica si esegue un calcolo lungo in modo ritardato (ovvero solo al momento del primo utilizzo del risultato). Il risultato di tale calcolo viene memorizzato in una variabile locale immodificabile in modo che il calcolo debba essere fatto una sola volta. La prima chiamata modifica la function stessa assegnandogli una nuova function che semplicemente ritorna il valore calcolato.
function calcoloLazy() { var l = 123;//potrebbe essere un calcolo lungo e complicato calcoloLazy = function() { return l; } return l; }
E finalmente la soluzione al quesito
Siamo arrivati in fondo a questo tutorial sul linguaggio JavaScript, vediamo di nuovo l’esempio visto all’inizio della prima parte:
String.prototype.r = function() { return this.split("").reverse().join('') } function a(b) { if (arguments.length == 1) { return b.slice(1, 3) + b[1]; } else if (arguments.length == 2) { b = arguments[0] + arguments[1]; } return (b || this).r(); } (function b() { alert(a.apply(" esoc") + a("mnopqrs") + a("ets", "ivaj ")); })()
Se avete seguito con attenzione questo tutorial questo codice dovrebbe sembrare chiarissimo (ora non esageriamo, diciamo che dovrebbe sembrare almeno leggibile!). Ecco la soluzione:
- la function
r
è aggiunta alprototype
diString
e permette di invertire una stringa - la function
a
manipola la stringa passata, il comportamento dipende dal numero di parametri passati - la function
b
viene invocata immediatamente grazie alle parentesi in fondo. Stampa la concatenazione di tre stringhe ottenute con tre chiamate alla functiona
In pratica eseguendo il codice viene mostrato un alert JavaScript immediatamente con scritto cose non javiste
, semplice no?
Come alla fine della primo post vi consiglio l’ottimo libro JavaScript: The good parts di Douglas Crockford se volete approfondire ulteriormente l’argomento:
Pingback: Fare una chiamata a un servizio json dalla nostra applicazione Android | devAPP()