Professional Documents
Culture Documents
enrico.denti@unibo.it
PERCH JAVASCRIPT ?
Perch incorpora molte caratteristiche interessanti, spesso ignote: ben oltre lo scripting per pagine web! Perch adotta segue un approccio funzionale, con funzioni e chiusure concetti fondamentali di computer science qui offerti in forma semplice e pratica Perch adotta un modello object-based senza classi, basato sul concetto di prototipo Perch un linguaggio interpretato, con aspetti dinamici di grande interesse ottima palestra per sperimentare Perch adatto a creare applicazioni multi-paradigma In breve, perch unisce potenza espressiva a un modello computazionale interessante, potente e flessibile !
l'ereditariet prototype-based concettualmente potente, ma non sempre facile da seguire per chi viene da linguaggi "class based"
1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA
JAVASCRIPT: LE BASI
Nasce come linguaggio di scripting
inventato da Netscape (LiveScript), rinominato in JavaScript nel 1995 dopo un accordo con Sun Microsoft ha la sua variante, JScript (minime differenze) standard: ECMAScript 262
Come si usa:
i programmi Javascript si includono di solito nel sorgente HTML delle pagine Web e sono eseguiti nel browser Javascript Virtual Machine: Rhino, SpiderMonkey (Mozilla: v. anche TraceMonkey), V8 (Google), JScript (Microsoft)
V8 (C++), Rhino (Java), SpiderMonkey (C) sono usabili anche stand alone
-->
Le costanti stringa possono essere delimitate sia da virgolette sia da apici singoli
se occorre annidare virgolette e apici, occorre alternarli: document.write('<IMG src="image.gif">') document.write("<IMG src='image.gif'>")
Le costanti numeriche sono sequenze di caratteri numerici non racchiuse da virgolette o apici
la costante NaN rappresenta il "risultato" di operazioni impossibili: non uguale ad alcun valore, incluso se stessa un'operazione che coinvolga un NaN d come risultato NaN la costante Infinity rappresenta un valore maggiore del massimo reale positivo rappresentabile (1.79 * 10+308)
OCCHIO a commentare blocchi di codice con espressioni regolari: in esse possono comparire stringhe come "*/"
Espressioni
Sono consentite sostanzialmente le stesse espressioni lecite in Java
espressioni numeriche: somma, sottrazione, prodotto, divisione (sempre fra reali), modulo, shift, espressioni condizionali con ? : espressioni stringa: concatenazione con + espressioni di assegnamento: con = (e sue varianti)
Esempi:
document.write(18/4) // fra reali document.write(18%2) document.write("paolino" + 'paperino')
Variabili
Le variabili in Javascript sono loosely typed possibile assegnare alla stessa variabile prima un valore di un tipo, poi un valore di un altro tipo! Ad esempio: alfa = 19 beta = "paperino" alfa = "zio paperone" // tipo diverso!! Sono consentiti incrementi, decrementi e operatori di assegnamento estesi (++, --, +=, )
La dichiarazione implicita introduce sempre e solo variabili globali La dichiarazione esplicita pu introdurre variabili globali o locali, a seconda di dove compare.
Tipo dinamico
Il tipo (dinamico) di una espressione (variabili incluse) ottenibile tramite l'operatore typeof : typeof(18/4) d number typeof("aaa") d string typeof(false) d boolean typeof(document) d object typeof(document.write) d function
anche se talvolta typeof restituisce risultati inattesi: ad esempio, typeof([1,2,3]) d object anzich array
Se usato su variabili, typeof restituisce il tipo attuale della variabile (o dell'oggetto): a = 18; typeof(a) d number a = "ciao"; typeof(a) d string
Istruzioni
Le istruzioni devono essere separate una dall'altra o da un fine riga, o da un punto e virgola (similmente al Pascal e diversamente dal C o da Java) Esempio:
alfa = 19 // fine riga beta = "paperino" ; gamma = true document.write(beta + alfa)
ATTENZIONE alle conseguenze: ("semicolon insertion") se si va a capo dove non si deve Esempio:
return 18; return 18; // restituisce 18 // restituisce undefined // (statement irraggiungibile)
Strutture di controllo
Javascript dispone delle usuali strutture di controllo if, switch, for, while, do/while pi due specifiche per operare su oggetti: for in e with ATTENZIONE: il for..in di JavaScript non equivalente al foreach di Java/C# poich itera sui nomi degli elementi della collezione, non sugli elementi stessi
per gli array, i nomi degli elementi sono gli indici (0,1,2) una caratteristica utile per gli array associativi, che per pu risultare fuorviante se sconosciuta o inattesa il for classico
v = [13,24,37]; for (i=0; i < v.length; i++) document.write( v[i] ); // stampa 13,24,37
il forin
v = [13,24,37]; for ( item in v ) document.write( item ); // stampa 0,1,2
Operatori relazionali
Le operatori relazionali sono i soliti (==, !=, >, <, >=, <=) pi due nuovi (===, !==), combinabili con gli usuali operatori logici AND (&&), OR (||), NOT (!)
ATTENZIONE: nella valutazione di condizioni si considera falso non solo false, ma ogni valore falsy ovvero anche null, undefined, la stringa vuota (''), il valore 0 ed NaN Ogni altro oggetto, inclusa la stringa 'false', vero.
I due operatori === e !== offrono una alternativa pi sensata al comportamento discutibile dei due ==, !=
1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA
Definizione di Funzioni
Le funzioni sono definite dalla keyword function
il nome opzionale: JavaScript ammette anche funzioni anonime, di fatto analoghe alle lambda-expression c' una sottile differenza fra function expression e function declaration la vedremo meglio pi avanti
Possono essere sia funzioni in senso proprio, sia procedure che non restituiscono nulla
ma non si usa la keyword void
10
Invocazione di funzioni
Le funzioni sono invocate nel solito modo, tramite l'operatore di chiamata (), fornendo i parametri attuali:
result = sum(18,-3);
Se i tipi non hanno senso per le operazioni fornite, l'interprete Javascript d errore a runtime
A differenza di C e Java, si pu definire una funzione dentro un'altra molteplicit di ambienti definiti e in essere a run-time concetto di chiusura (v.oltre)
11
Variabili e Scope
Lo scope delle variabili in Javascript : globale, per quelle definite fuori da funzioni e per quelle definite dentro a funzioni in modo implicito locale, per quelle definite dentro a funzioni in modo esplicito (ivi compresi i parametri passati) ATTENZIONE: un blocco NON delimita uno scope ! le variabili definite dentro a blocchi innestati sono comunque riferite all'ambiente che le contiene
x = '3' + 2; // la stringa '32' { { x = 5 } // blocco interno y = x + 3; // x denota 5, non "32" }
Esperimento
x = '3' + 2; // '32' { { x = 5 } // blocco y = x + 3; // x = 5 }
12
Esperimento 1
13
Esperimento 2
globale
locale
f locale = 4
ff globale = 4 globale =4
f globale = 3
In presenza di funzioni definite dentro altre funzioni, infatti, si crea una catena di ambienti di definizione innestati uno dentro l'altro: la catena lessicale A runtime, l'attivazione delle varie funzioni d poi luogo a una sequenza di ambienti attivi istante per istante, non necessariamente uguale alla precedente: la catena dinamica Il mondo si complica: DOVE vanno cercate le definizioni delle variabili (e delle funzioni..) via via utilizzate?
14
Il problema
Quando si usa un simbolo non definito in un certo ambiente, DOVE va cercata una definizione per esso? Ci sono due possibilit: cercarla nell'ambiente in cui la funzione fisicamente definita, seguendo quindi la catena lessicale cercarla nell'ambiente attivo al momento della chiamata, seguendo quindi la catena dinamica Le due scelte corrispondono ad adottare due modelli computazionali diversi. Per capire il problema, si consideri l'esempio seguente.
Esperimento 3
Si consideri il seguente programma Javascript:
var x = 20; function provaEnv(z) { return z + x; } alert(provaEnv(18)) // visualizza certamente 38 function testEnv() { var x = -1; return provaEnv(18); // COSA CALCOLA ? QUALE x USA? }
Nella funzione testEnv si ridefinisce il simbolo x, poi si invoca provaEnv, che usa il simbolo ma QUALE x ? il simbolo x nell'ambiente in cui provaEnv definita ha infatti un significato diverso rispetto a quello che ha nell'ambiente in cui provaEnv chiamata!
15
se si sceglie invece di considerare l'ambiente di definizione dinamico, si dice che il modello computazionale adotta la CHIUSURA DINAMICA
in tal caso, x varr -1 e la funzione restituir 17
16
Esperimento 3 (segue)
17
Chiusure
In generale, per chiusura si intende una funzione con variabili libere, che vengono legate in un ambiente
origine: linguaggi funzionali come Lisp, Scheme (~1960) dove si trovano: tipicamente sono disponibili in linguaggi in cui le funzioni sono entit di prima classe (ossia passabili come argomenti, restituite da funzioni, assegnate a variabili, etc)
Il tempo di vita di tali variabili pari almeno a quello della chiusura, anche se la funzione termina prima
in linguaggi SENZA chiusure, il tempo di vita di una variabile locale coincide con quello della funzione che la contiene in linguaggi CON chiusure, invece, una variabile locale pu sopravvivere oltre la funzione che la contiene e definisce, se referenziata da un'altra funzione interna che ne fa uso. (tipicamente si fa uso di un garbage collector per ripulire)
18
19
20
21
} var arrayDiDueFunzioni = myenv(); var s1 = arrayDiDueFunzioni[1](); // "bla bla" arrayDiDueFunzioni[0]("buuh"); var s2 = arrayDiDueFunzioni[1](); // "buuh"
i = 1; loop( function(){ i++ }, 10)(); document.write(i); // 11 In questo modo ci si possono inventare strutture di controllo qualunque (ad esempio, cicli a passo step, o altro..)
22
23
24
public void calc(final int arg){ new Thread(new Runnable(){ public void run(){ result += 2; // accede al contesto lessicale della classe che la contiene result += arg; // accede in sola lettura al contesto lessicale del
// metodo che la contiene (solo variabili final)
}}).start(); }
25
public static void main(String[] args){ sintassi innaturale Esterna esterna = new Esterna(); esterna.Interna interna = esterna.new Interna(); // accede a ogni variabile di ogni istanza della classe esterna for(int i = esterna.state; (i = interna.incr() ) < 5; ) System.out.println(i); } }
26
.e in Java8 (expected):
{ { { { int x -> x+1 } int x, int y -> x+y } -> e+55 } o anche solo: { e+55 } System.out.println("hello") }
Vantaggi:
ci si libera del codice "verboso" tipico delle classi anonime si possono alleggerire molti costrutti e pattern di largo uso
in particolare si mira ad alleggerire i "vertical problems" (5-6 linee di codice solo per incapsulare una singola istruzione) evitando per di trasformarli in "horizontal problems" (singole righe di codice troppo lunghe e quindi incomprensibili per diverso motivo) riducendo al contempo l' "horizonal noise" (sintassi sovrabbondante).
utile integrazione con delegati (C#) e method references (Java8) nonch con extension methods (C#, Java8)
27
Come in un delegato C#, il metodo referenziato da un method reference pu essere statico o di istanza
nel primo caso, basta il metodo stesso nel secondo, occorre specificare anche l'istanza associata
28
Idea: scrivere i metodi "aggiuntivi" altrove, come metodi statici di un'altra classe, ma etichettandoli in modo da poterli usare "logicamente" come metodi standard Per farlo, si stabilisce che:
il PRIMO ARGOMENTO di tali metodi sia l'oggetto target dell'invocazione e indichi col suo tipo la classe da estendere, di cui cio il metodo esteso andr a fare logicamente parte SINTASSI C#: primo argomento etichettato con this SINTASSI Java8: primo argomento senza qualifiche particolari
29
cliente
Counter c = new Counter(7); int val = c.getValue(); // ovvio // EXTENSION METHOD! string s = c.getValueAsString();
Java8
class MyExtensions { public static String getValueAsString(Counter c) { return "" + c.getValue() } }
Gli extension methods consentono di riscrivere pattern di largo uso in modo particolarmente efficace e conciso
purch naturalmente le API fondamentali (in primis: JCF) siano adeguate di conseguenza, per sfruttare le nuove possibilit
30
}}).start(); }
in futuro (Java 8)
new Thread(#(){ result += 2; resutl += arg; }).start(); }
// accede al contesto lessicale della classe che la contiene // accede in sola lettura al contesto lessicale del metodo // che la contiene (solo variabili final)
}}); in futuro (Java 8) PASSO 1 Collections.sort(mylist, #{ Point p1, Point p2 -> // horizontal problem p1.getX()<p2.getX() ? -1 : (p1.getX()>p2.getX() ? 1 : 0) } ); in futuro (Java 8) PASSO 2 (con librerie rivisitate) Collections.sortBy(mylist, #{ Point p -> p.getX() } ); dove sortBy prevede come secondo argomento non pi un Comparator, ma soltanto un Extractor che si limiti a estrarre una chiave di confronto (Comparable). in futuro (Java 8) PASSO 3 (con librerie rivisitate e method reference) Collections.sortBy(mylist, #Point.getX ); in futuro (Java 8) PASSO 4 (con extension methods) mylist.sortBy( #Point.getX ); // o forse Point#getX
31
in Java8, il codice equivalente si basa su una classe anonima derivata dall'interfaccia SAM (Single Abstract Method) associata all'espressione lambda
Conseguentemente:
in C#, le lambda expression sono assimilate a normali interfacce, con piena interoperabilit con tutte le API esistenti in Java8, le lambda expression sono gestite diversamente e ristrette a situazioni specifiche (SAM-convertible contexts), ma non per questo sono meno potenti.
32
33
Tuttavia, valutando i parametri a priori pu causare inefficienze e soprattutto fallimenti non necessari
infatti, valutando i parametri sempre e comunque, pu fare lavoro inutile se alcuni di essi non risultano poi necessari in quella chiamata ma soprattutto, se la valutazione di uno di tali parametri d errore (o eccezione), questo modello causa un fallimento che si sarebbe potuto evitare con un approccio diverso.
In questo esempio, la valutazione dei parametri attuali causa eccezione, ma in realt in questa chiamata il parametro b non sarebbe stato usato perch x>y vera fallimento evitabile! Con un diverso modello computazionale, questa invocazione avrebbe potuto avere successo!
34
Di nuovo, il modello computazionale applicativo causa un fallimento non necessario, in quanto valuta tutti i parametri a prescindere dalla loro reale utilit nella chiamata corrente In questo caso, infatti, flag<10 vera (perch il valore passato per flag 3) e dunque la funzione non avrebbe mai usato x Di nuovo, con un diverso modello computazionale questa chiamata avrebbe potuto avere successo!
IL MODELLO CALL-BY-NAME
Il modello applicativo non il solo possibile: in particolare, il "modello normale" (introdotto da Algol60) adotta un meccanismo di valutazione noto come Call-By-Name Nel modello Call-by-name:
i parametri si valutano NON all'atto della chiamata, ma solo al momento dell'effettivo uso e quindi solo se servono alla funzione si passano quindi NON valori (o indirizzi) ma bens oggetti computazionali ("eseguibili") ergo, un parametro ricevuto potrebbe non essere mai valutato se in quella invocazione non c' effettivo bisogno di lui si evita di fallire inutilmente
Conseguenza: l'insieme di funzioni che terminano con successo con questo modello pi ampio rispetto al modello applicativo.
35
36
MA ALLORA.. PERCH?
MA.. se il modello call-by-name cos utile, perch nei linguaggi di largo uso non stato adottato questo? Perch, nonostante i suoi vantaggi concettuali, meno efficiente nei casi normali, che sono la maggioranza
valuta i parametri pi volte per ogni chiamata (anche se si potrebbe ovviare valutandoli solo al primo uso) richiede pi risorse a run-time, dovendo gestire il passaggio di oggetti computazionali anzich semplici valori o indirizzi richiede una macchina virtuale pi sofisticata, capace di "lazy evaluation" il tutto per catturare "pochi" (?) casi .. che magari sono pure frutto di errori di programmazione..?
Inoltre, molti casi di fallimento (quelli aritmetici) si evitano arricchendo l'aritmetica con NaN e Infinity
A differenza di quanto ci si potrebbe aspettare, questo esempio non fallisce perch la macchina virtuale JavaScript non d errore nella divisione 3/0, che un'operazione lecita con risultato Infinity Perci, il programma funziona:
result = 4
37
38
FUNZIONA !
Esperimento
Un'ottimizzazione del modello "call by name", che evita di ricalcolare n volte la stessa funzione, detta "call by need".
errore
nessun errore
39
40
public static void main(String[] args) { costruisce oggetti (singleton) int x=5, y=4; printTwo(x>y, new MyFunction1(), new MyFunction2() ); } }
result = 4.0 FUNZIONA ! MyFunction un'interfaccia (o una classe astratta) che dichiara il metodo invoke classi definiscono metodi che restituiscono i valori desiderati MyFunction1 e MyFunction1 sono le due classi concrete che definiscono invoke rispettivamente come return 3+1; e return 3/0
dove:
static void printTwo(bool cond, MyFunction a, MyFunction b){ if (cond) System.Console.WriteLine("result = " + a() ); else System.Console.WriteLine("result = " + b() ); } public static void Main(string[] args) { int x=5, y=4, a=0; printTwo(x>y, () => 3+1, () => 3/a ); } }
lambda expressions: costruiscono funzioni
invoca funzioni
result = 4.0
NB: necessario usare una variabile per evitare la valutazione di corto circuito a compile time (l'efficienza di C#...)
41
1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA
Il Modello a Oggetti
Javascript adotta un modello object-based (non objectoriented), senza classi, basato sul concetto di prototipo. In questo approccio, un oggetto: NON istanza di una classe (non esistono classi!) solo una collezione di propriet (dati o funzioni), tutte pubbliche, accessibili tramite "dot notation" costruito, tramite new, da un costruttore che ne specifica la struttura (iniziale)
il nome del costruttore "quanto resta" delle classi
associato dal costruttore a un "oggetto padre", detto prototipo, di cui eredita le propriet PROTOTYPE-BASED INHERITANCE
42
Costruzione di oggetti
Un costruttore una normale funzione, che per: specifica le propriet e i metodi dell'oggetto da creare mediante la parola chiave this, con ci distinguendole dalle propriet e dai metodi della funzione stessa:
Point = function(i,j){ this.x = i; this.y = j; this.getX = function(){ return this.x; } this.getY = function(){ return this.y; } }
Esperimenti
43
44
anche possibile rimuovere dinamicamente propriet, mediante l'operatore delete : delete p1.x // da {x:10, y:4, z: -3} diventa {y:4, z: -3}
La propriet constructor
Ogni oggetto tiene traccia del costruttore che lo ha creato mediante la propriet constructor
RICORDA: questo non autorizza a dedurre che l'oggetto abbia ancora tutte e sole le propriet definite dal costruttore perch nel frattempo la situazione pu essere cambiata! Ora l'oggetto potrebbe averne di pi (se ne nono state aggiunte) o di meno (se ne sono state tolte)
p1 ha le due propriet definite dal costruttore ora ne ha 3, ma il costruttore sempre lo stesso
45
Invocazione di metodi
Un metodo invocato dall' operatore di chiamata () document.write( p1.getX() + "<br/>") NB: un metodo inesistente provoca errore a run-time (metodo non supportato) ma non sempre viene mostrato un messaggio d'errore Attenzione, per: in caso di errore, Javascript non esegue le istruzioni successive.
46
Prototipi di oggetti
Ogni oggetto associato a un "oggetto padre", detto prototipo, di cui eredita le propriet Ci d luogo a una forma particolare di ereditariet, senza classi, detta prototype-based inheritance
con le relative "gerarchie di ereditariet prototipale"
Ogni oggetto referenzia il suo prototipo tramite una propriet (nascosta) chiamata __proto__
Oggetto oggetto padre
47
Esso antenato DIRETTO di ogni "object literal" e antenato INDIRETTO di ogni altro oggetto
gli oggetti costruiti da un dato costruttore hanno infatti come padre una sorta di "prototipo di classe" che a sua volta ha come padre "The One".
__proto__ x: 6, y: 7
Oggetto p1
The One
__proto__ x: 3, y: 4
Oggetto p2
The Point
__proto__ x: 0, y: 1
48
Ma questi prototipi sono anch'essi degli oggetti, quindi hanno a loro volta un prototipo: il caro The One! Da qui nasce la tassonomia di prototipi che esprime l'ereditariet nella forma prototype-based.
__proto__ x: 6, y: 7
Oggetto p1
The One
The Function
Funzione Point
__proto__ x: 3, y: 4
Oggetto p2
The Point
Funzione Point: ha come padre "The Function".
__proto__ codice
__proto__ x: 0, y: 1
49
Tassonomia di Prototipi
Prototipo-base di qualunque oggetto
The One
The Function
The Array
The Number
The Point
Esperimenti (1)
Google Chrome contiene un motore JavaScript che mostra anche la propriet __proto__, che altri motori tipicamente nascondono: ci lo rende perfetto per sperimentare. Usiamo l'ambiente JScript IDE disponibile sul sito del corso Sfrutteremo l'operatore typeof per scoprire il tipo di un oggetto, e l'operatore isPrototypeOf per risalire la catena dei prototipi.
isPrototypeOf(obj) controlla se l'oggetto corrente nella catena dei prototipo di obj
Oggetto obj
Prototipo di obj
__proto__
50
Esperimenti (2)
funz = function(x,y){ return x+y; } Point = function(i,j){ this.x=i; this.y=j; this.getX= } typeof(funz) protfunz = funz.__proto__ typeof(protfunz) typeof(protfunz.__proto__) protfunz.isPrototypeOf(funz) function function object true
Funzione funz
protfunz.isPrototypeOf(funz) vera
__proto__ codice
typeof(funz) function
The Function
protfunz lui typeof(protfunz) sempre function
The One
typeof(protfunz. __proto__) object
Esperimenti (3)
funz = function(x,y){ return x+y; } Point = function(i,j){ this.x=i; this.y=j; this.getX= } typeof(funz) protfunz = funz.__proto__ typeof(protfunz) typeof(protfunz.__proto__) protfunz.isPrototypeOf(funz) function function object true
Funzione funz
__proto__ codice
The Function
The One
In questo sistema, il padre di tutte le funzioni, da noi chiamato affettuosamente "The Function", la curiosa funzione function Empty(){}
51
Esperimenti (4)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4) pr = p1.__proto__ typeof(p1) typeof(pr) pr.isPrototypeOf(p1) p1.constructor Funzione Point __proto__
codice
Oggetto p1 __proto__ constructor
The Function
Oggetto pr
The One
The Point
x: 3, y: 4
Esperimenti (5)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; pr0 = p0.__proto__; p1 = new Point(3,4); pr1 = p1.__proto__; p2 = new Point(0,1); pr2 = p1.__proto__; pr1 == pr2 true pr0 == pr2 false pr1.__proto__ === pr0 true
p0
__proto__ x: 6, y: 7
pr0
The One
pr1
p1
__proto__ x: 3, y: 4
The Point
pr2
p2
__proto__ x: 0, y: 1
52
Esperimenti (6)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } Pair = function(a,b){ this.a=a; this.b=b; } p0 = { x:6, y:7 }; pr0 = p0.__proto__; Pair.__proto__ === Point.__proto__ true Point.__proto__.__proto__ === pr0 true Pair.__proto__.__proto__ === pr0 true
p0
__proto__ x: 6, y: 7
pr0
The One
pr1
The Function
p1
__proto__ x: 3, y: 4
The Point
pr2 Pair Point
p2
__proto__ x: 0, y: 1
__proto__ codice
__proto__ codice
Prototipi di costruzione
Sappiamo che il prototipo di un oggetto dipende da chi lo costruisce: in particolare, gli oggetti costruiti da uno stesso costruttore condividono tutti lo stesso prototipo
una sorta di "prototipo di classe"
Il prototipo che un costruttore attribuisce agli oggetti da lui creati espresso dalla propriet prototype
prototype una propriet (pubblica) dei soli costruttori
non va confusa con la propriet __proto__ , che invece caratteristica di qualunque oggetto (e non pubblica)
53
In particolare, detto Object il costruttore degli oggetti, Object.prototype referenzia "The One", l'antenato comune (innominato) di tutta la tassonomia JavaScript!
MA ATTENZIONE: Object in s un costruttore, ossia una funzione.. il cui __proto__ quindi "The Function", non Object! In questo mix facile perdersi
54
Esperimenti (7)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; pr0 = p0.__proto__; Object.prototype === pr0 true pr0.__proto__ == null true Function.prototype === Point.__proto__ true Object.__proto__ === Function.prototype true Point.prototype == pr1 true
p0
__proto__ x: 6, y: 7
pr0
The One
Object.prototype
The Function
Function.prototype
p1
__proto__ x: 3, y: 4
pr1 pr2
The Point
Pair Point
p2
__proto__ x: 0, y: 1
__proto__ codice
__proto__ codice
Esperimenti (8)
Osservare come NON sia Object la radice della gerarchia, ma bens "The One", alias Object.prototype Object "solo" un costruttore con un nome evocativo.. ossia, una normale funzione! "The One" p0 Object
__proto__ x: 6, y: 7
pr0
Object.prototype
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
p1
__proto__ x: 3, y: 4
pr1 pr2
"The Point"
Point.prototype
__proto__
propriet di tutti i Point
Point
p2
__proto__ x: 0, y: 1
55
Esperimenti (9)
Con l'interprete JavaScript Rhino, dotato di debugger, possiamo vedere che Object.prototype definisce le propriet comuni a tutti gli oggetti:
Esperimenti (10)
Analogamente Function.prototype definisce le propriet comuni a tutte le funzioni:
56
il modo corretto accedere al prototipo tramite la propriet prototype del corrispondente costruttore.
57
Esempio
Consideriamo il solito esempio con i soliti tre punti (un "object literal" e due costruiti dal costruttore Point):
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; showProps(p0); p1 = new Point(3,4); showProps(p1); p2 = new Point(0,1); showProps(p2);
possibile aggiungere propriet a Point.prototype, ottenendo con ci l'effetto di farle ereditare immediatamente e dinamicamente ai due Point p1 e p2 gi esistenti
la modifica non impatta invece su p0, che fa riferimento a un prototipo diverso
Point.prototype.color = "black"
showProps(p1); showProps(p0); // ha anche la nuova propriet color! // rimasto com'era
Esperimenti (11)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; p1 = new Point(3,4); p2 = new Point(0,1);
Point.prototype.color = "black"
nessun impatto qui
"The One"
Object.prototype
"The Function"
Function.prototype
p0
__proto__ x: 6, y: 7
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
p1
__proto__ x: 3, y: 4
"The Point"
Point.prototype
percepito qui
__proto__
propriet di tutti i Point color:"black"
Point
p2
__proto__ x: 0, y: 1
percepito qui
58
Esempio
Riprendendo il solito esempio:
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4); showProps(p1); p2 = new Point(0,1); showProps(p2);
Se ora, dopo aver gi creato p1 e p2, sostituiamo il prototipo di costruzione di Point reimpostando la propriet Point.prototype, tutti i futuri Point risulteranno diversi
i "Point futuri" non risulteranno "imparentati" coi "Point vecchi", facendo parte di diverse catene prototipali
Point.prototype = { color: "black", setX: function(x){ this.x = x; } }; } showProps(p1); // rimasto tutto com'era p3 = new Point(2,6); showProps(p3); // ha anche le nuove propriet! p3.setX(9); // funziona e cambia l'ascissa a 9 !!
59
Esperimenti (12a)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4);
"The One"
Object.prototype
"The Function"
Function.prototype
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
"The Point"
Point.prototype
p1
__proto__ x: 3, y: 4
__proto__
propriet di tutti i Point
Point
Esperimenti (12b)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4); Point.prototype = { color:"black", setX:function(x){ this.x = x; } }; p3 = new Point(2,6); p3.setX(9)
nuovo prototipo "The One"
Object.prototype
"The Function"
Function.prototype
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
"The Point"
Point.prototype
p1
__proto__ x: 3, y: 4
__proto__
propriet di tutti i Point
Point
p3
__proto__ x: 2, y: 6
60
Per avere oggetti in relazione padre/figlio ("is a") occorre impostare i prototipi di costruzione dei costruttori in modo da riflettere la tassonomia desiderata.
l'ereditariet fra classi dei linguaggi class-based diviene qui un'ereditariet prototipale espressa da prototype
Esempio (1)
Supponiamo di voler esprimere l'idea che la categoria Studente "erediti" dalla categoria Persona Occorre definire il costruttore Persona in accordo alle propriet desiderate per le persone. Occorre poi definire il costruttore Studente in accordo alle propriet desiderate per gli studenti.
ma se ci fermiamo qui, Studente una categoria "stand-alone", non imparentata con Persona
Per esprimere il fatto che Studente "is-a" Persona, occorre far s che gli oggetti-studente che saranno costruiti abbiano come prototipo una persona
.. e non "The Student" o un altro oggetto che non ha nulla a che fare con le persone
61
Esempio (2)
Per far s che gli oggetti-studente che saranno costruiti abbiano come prototipo una persona, occorre: scegliere un oggetto-persona che faccia da prototipo per tutti gli studenti (che sia bello) impostare su esso Studente.prototype, in modo che tutti gli Studenti che verranno creati facciano riferimento a tale Persona. per mantenere la coerenza globale, reimpostare parallelamente la propriet constructor della persona prototipo (che ovviamente punterebbe al costruttore Persona) in modo che punti al costruttore Studente
in tal modo, chiedendo a un oggetto Studente chi sia il suo constructor, risponder correttamente Studente
Esempio (3)
Per prima cosa, definiamo il costruttore Persona:
Persona = function(nome, annoNascita){ this.nome = nome; this.anno = annoNascita; this.toString = function(){ return this.nome + " nata nel " + this.anno } }
62
Esempio (4)
Ora procuriamoci una specifica (e bella!) Persona, destinata a fare da prototipo a tutti gli studenti:
personaBellissima = new Persona("zio", 1900);
Finalmente impostiamo il prototipo di costruzione di Studente in modo che faccia riferimento ad essa: Studente.prototype = personaBellissima; Last but not least, per garantire la coerenza interna, impostiamo la propriet constructor del neo-prototipo in modo che punti al costruttore Studente: Studente.prototype.constructor = Studente
Esperimenti (13a)
"The One"
Object.prototype
"The Function"
Function.prototype
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
"The Person"
Persona.prototype
Persona
__proto__
propriet di tutte le Persone
"The Student"
Studente.prototype
__proto__
propriet di tutti gli Studenti
63
Esperimenti (13b)
"The One"
Object.prototype s = new Studente( "John", 1995,9000234532)
"The Function"
Function.prototype
__proto__ : null
propriet di tutti gli oggetti
__proto__
propriet di tutte le funzioni
__proto__
nome: "John" anno: 1995
matricola: 9000234532
"The Person"
Persona.prototype
Persona
__proto__
propriet di tutte le Persone
personaBellissima
VECCHIO
Studente.prototype
__proto__
propriet di tutti gli Studenti
Esperimenti (13c)
personaBellissima.hasOwnProperty("constructor") s.hasOwnProperty("constructor") s.constructor true false Studente
__proto__
nome: "John" anno: 1995
matricola: 9000234532
"The Person"
Persona.prototype
Persona
__proto__
propriet di tutte le Persone
personaBellissima
VECCHIO
Studente.prototype
__proto__
propriet di tutti gli Studenti
64
65
L'Oggetto Globale
Si visto che JavaScript ammette variabili e funzioni globali , ossia non definite dentro un'altra funzione o di un oggetto. Ma dov' questo "ambiente globale"? In realt, non esiste un ambiente globale nel senso tradizionale del termine: semplicemente, variabili e funzioni "globali" sono attribuiti a un oggetto globale , avente
come metodi, tutte le funzioni predefinite pi tutte quelle definite dall'utente a livello "globale" come dati, tutte le variabili globali
Curiosamente, l'oggetto globale deve esistere, ma non prestabilito n CHI sia, n CHE NOME debba avere: dipende dal contesto e dallo specifico motore JavaScript.
66
unescape
67
definisce i concetti per esprimere date e orari fornisce il supporto per le espressioni regolari. contiene la libreria matematica (non va istanziato)
1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA
68
Il costruttore Function
In precedenza abbiamo discusso molto di funzioni. Ora sappiamo che ogni funzione JavaScript un oggetto, costruito (direttamente o meno) dal costruttore Function In particolare, tale costruzione pu avvenire: implicitamente, quando si usa il costrutto function (come abbiamo fatto sempre finora)
in tal caso, gli argomenti sono i parametri formali della funzione e il corpo (codice) della funzione racchiuso in un blocco
Esempi
Esempio di costruzione implicita square = function(x){ return x*x }
gli argomenti sono i parametri formali della funzione il corpo della funzione cablato nel codice e racchiuso in un blocco
69
Esempi
Esempio di costruzione implicita square = function(x){ return x*x }
gli argomenti a partireparametri formali della funzione sono i da dati embedded nel programma Costruisce efficiente, perch valutato una nel codice il corpo della funzione cablatosola volta ma non flessibile, perch e racchiuso in un blocco il corpo cablato nel codice
70
perch gli argomenti sono stringhe e quindi l'operatore + significa concatenazione di stringhe, non somma!
perch l'argomento x ora un intero (typeof(x)=number), quindi l'operatore + ha il significato di somma fra numeri.
71
Metodi invocabili su un oggetto funzione toString - una stringa fissa (ma si pu cambiare) valueOf - ritorna la funzione stessa call e apply supportano i pattern di chiamata indiretta
72
obj.method(args)
si scambiano le parti, invocando sull'oggetto-metodo method il metodo call o apply con primo argomento l'oggetto obj e successivi argomenti args, secondo i pattern
obj.method(args)
I due pattern si differenziano si scambiano le parti, invocando sull'oggetto-metodo solo per il diverso formato method il metodo call o apply con primo argomento l'ogdegli argomenti args. getto obj e successivi argomenti args, secondo i pattern
73
2 caso
x = 88 function Obj(u){ this.x = u } obj = new Obj(-4) prova.call(obj,3)
Restituisce 3 + 88 = 91
Restituisce 3 + -4 = -1
74
Costruttori di Array
Un array Javascript un'entit a met strada fra un array e una lista
come in Java, gli elementi si numerano da 0, length d la lunghezza dell'array, si usa la notazione parentesi quadre a differenza di Java, qui per length d la lunghezza dinamica (attuale) dell'array e non c' il vincolo di omogeneit in tipo: le celle contengono oggetti - cio qualunque cosa
Tale mapping oggetti / array si spinge al punto da permettere la notazione "array-like" per accedere per nome alle propriet degli oggetti
nel quotidiano, per accedere alla propriet x dell'oggetto obj si usa la notazione puntata nella forma obj.x tuttavia, questo approccio richiede di sapere gi che l'oggetto abbia una propriet x: il nome cablato nel codice al contrario, la notazione array-like permette di accedere a propriet di oggetti senza cablarne il nome nel codice
75
Viceversa, propriet di nome NON noto a priori sono accessibili solo con la notazione array-like:
scrivendo obj.[propname] dove propname una stringa che conterr a run time il nome delle propriet di interesse.
Introspezione
La possibilit di aggiungere /togliere dinamicamente propriet a un oggetto pone il problema di scoprire quali propriet esso abbia in un dato istante, ossia di procedere alla sua introspezione A questo fine offerto il costrutto:
76
Da Introspezione a Intercessione
Con il costrutto introspettivo for(..in..) possibile scoprire i nomi dele propriet (visibili) di un oggetto:
function show(ogg){ for (var pName in ogg) document.write("propriet: " + pName +"<BR>") }
Per accedere a tali propriet a partire dai nomi della propriet stesse serve appunto la notazione array-like! Ad esempio, per elencarne i tipi, si pu scrivere:
function show(ogg){ for (var pName in ogg) { document.write("propriet: " + pName + ", tipo " + typeof(ogg[pName]) + "<BR>") }
p1
p2
77
1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA
78
Usabile sia come interprete stand-alone, sia come componente interoperabile con Java Programmazione multi-linguaggio e multi-paradigma
Accesso da JavaScript a package e classi Java
Scripting Java
JavaScript
Java
Embedding Rhino
79
Riferimenti:
http://www.mozilla.org/rhino/scriptjava.html https://developer.mozilla.org/en/Scripting_Java
80
81
Oggetto JavaScript che estende le classi e implementa le interfacce. Lereditariet usa gli stessi meccanismi di Java (ereditariet multipla delle sole interfacce)
82
Uno Scope un set di oggetti JavaScript che mantiene le informazioni script-specific sull'ambiente di esecuzione:
Variabili globali dello script; Oggetti standard (Function, Object...)
83
84