Introduzione
Perchè una reintroduzione? Perchè JavaScript ha la ragionevole pretesa di essere il linguaggio di programmazione più frainteso del mondo. Benchè spesso considerato ironicamente come un giocattolo, la sua ingannevole semplicità nasconde alcune potenti caratteristiche. Il 2005 vide il lancio di diverse applicazioni di alto profilo, che mostravano come la conoscenza profonda di questa tecnologia sia un importante abilità per qualunque sviluppatore web.
E' utile iniziare con un accenno alla storia del linguaggio. JavaScript fu creato nel 1995 da Brendan Eich, un ingegnere presso Netscape, e rilasciata per la prima volta con Netscape 2 all'inizio del 1996. Originariamente dove essere chiamato LiveScript, ma fu rinominato in per una fatalmente dannosa decisione di marketing che tentava di approfittare della popolarità del linguaggio Java della Sun Microsystem — nonostante abbiano molto poco in comune. Questa è stata una fonte di confusione da allora.
Microsoft rilasciò una versione più compatibile del linguaggio chiamata JScript con IE 3 alcuni mesi dopo. Netscape sottomise il linguaggio alla Ecma International, una organizzazione europea per gli standard, che risulta nella prima edizione degli standard ECMAScript nel 1997. Lo standard recevette un significativo aggiornamento come ECMAScript edition 3 nel 1999, ed è rimasto più o meno stabile da allora. La quarta edizione fu abbandonata a causa differenze di politiche a proposito della complessità del linguaggio. Molte parti della quarta edizione formano la base del nuovo ECMAScript edizione 5, pubblicato nel dicembre del 2009.
Questa stabilità è una grande notizia per gli sviluppatori, in quanto ha dato alle varie implementazioni diverso tempo per recuperare. Ci si concentrerà quasi esclusivamente sul dialetto dell'edizione 3. Per familiarità continueremo a chiamarlo Javascript dappertutto.
Diversamente dalla maggior parte dei linguaggi di programmazione, il linguaggio JavaScript non ha il concetto di input e output. E' stato disegnato per girare come un linguaggio di scripting in un ambiente ospite, ed è responsabilità dell'ambiente ospite di fornire meccanismi per comunicare con il mondo esterno. L'ambiente ospite più comune è il browser, ma l'interprete JavaScript può essere trovato anche in Adobe Acrobat, Photoshop, motore Widget di Yahoo! , e addirittura in ambienti lato server.
Panoramica
JavaScript è un linguaggio dinamico orientato agli oggetti; esso ha tipi e operatori, oggetti nucleo, e metodi. La sua sintassi deriva dai linguaggi Java e C, quindi molte strutture da questi linguaggi ricorrono anche in JavaScript. Una delle differenze chiave è che JavaScript non ha classi; invece, la funzionalità di classe è realizzata dai prototipi oggetto. L'altra differenza principale è che le funzioni sono oggetti, dando alle funzioni la capacità di mantenere codice eseguibile ed essere passate in giro come ogni altro oggetto.
Cominciamo guardando il blocco di costruzione di qualsiasi linguaggio: i tipi. I programmmi JavaScript manipolano valori, e tutti quei valori appartengono ad un tipo. I tipi JavaScript sono:
... oh, e Undefined (indefiniti) e Null (nulli), che sono leggermente strani. E gli Array, che sono un tipo speciale di oggetto. E Date ed Espressioni regolari, che sono oggetti che si ottengono gratuitamente. E per essere tecnicamente precisi, le funzioni sono solo un tipo speciale di oggetto. Quindi il diagramma dei tipi somiglia più a questo:
- Numeri
- Stringhe
- Booleani
- Oggetti
- Funzioni
- Array
- Date
- ExpReg
- Null
- Undefined
E ci sono anche alcuni tipi nativi di Errori. Comunque, le cose sono molto più semplici se ci atteniamo al primo diagramma
Numeri
I numeri in JavaScript sono in formato 64-bit a doppia precisione valore del IEEE 754, secondo le specifiche. Questo ha qualche interessante conseguenza. Non esiste una cosa come un intero in JavaScript, quindi bisogna stare un pò attenti con la vostra aritmetica, se siete abituati alla matematica in C o Java. Stare attenti a cose come:
0.1 + 0.2 == 0.30000000000000004
In pratica, i valori interi sono trattati come int a 32-bit (e sono memorizzati in questo modo in alcune implementazioni dei browser), che può essere importante per operazioni in bit. Per dettagli, consulta La Guida Completa sui Numeri JavaScript.
Gli operatori numerici standard sono supportati, incluso addizione, sottrazione, modulo (o resto) aritmetico e così via. Vi sono anche oggetti nativi che non sono stati menzionati prima, chiamati Math per trattare funzioni matematiche più avanzate e costanti:
Math.sin(3.5); var d = Math.PI * r * r;
E' possibile convertire una stringa in un intero utilizzando la funzione nativa parseInt()
. Questo prende la base per la converzione come secondo argomento opzionale, che si dovrebbe fornire sempre:
> parseInt("123", 10) 123 > parseInt("010", 10) 10
Se non si fornisce la base, si possono ricevere risultati inattesi:
> parseInt("010") 8
Questo succede perchè la funzione parseInt
ha deciso di trattare la scringa come un ottale a causa del primo 0.
Se si vuole convertire un numero binario in un intero, basta cambiare la base:
> parseInt("11", 2) 3
Similmente, è possibile analizzare numeri in virgola mobile usando la funzione nativa parseFloat()
che utilizza sempre la base 10 diversamente dalla cugina parseInt()
.
E' inoltre possibile utilizzare l'operatore unario +
per convertire valori in numeri:
> + "42" 42
Un valore speciale chiamato NaN
(abbreviazione per "Not a Number" - Non un Numero) viene ritornata se la stringa è non numerica:
> parseInt("hello", 10) NaN
Il NaN
è tossico: se viene fornito come imput a qualsiasi operazione matematica, il risultato sarà anch'esso NaN
:
> NaN + 5 NaN
E' possibile verificare se NaN
usando la funzione nativa isNaN()
:
> isNaN(NaN) true
Anche JavaScript ha i valori speciali Infinity
e -Infinity
:
> 1 / 0 Infinity > -1 / 0 -Infinity
E' possibile analizzare i valori Infinity
, -Infinity
e NaN
usando la funzione nativa isFinite()
:
> isFinite(1/0) false > isFinite(-Infinity) false > isFinite(NaN) false
parseInt()
e parseFloat()
analizzano una stringa finchè raggiungono un carattere che è invalido per il formato numerico specificato, quindi ritornano il numero analizzato fino a quel punto. Tuttavia l'operatore "+" converte semplicemente la stringa a NaN
se è presente in essa qualche carattere invalido. E' sufficiente provare ad eseguire l'analisi della stringa "10.2abc" con ogni metodi da soli, utilizzando la console e sarà possibile capire meglio le differenze.Stringhe
Le stringhe in JavaScript sono sequenze di caratteri. Più precisamente, sono sequenze di Caratteri Unicode, con ogni carattere rappresentato da un numero a 16-bit. Questa dovrebbe essere una buona notizia per tutti coloro che hanno avuto a che fare con l'internazionalizzazione.
Se si vuole rappresentare un singolo carattere, occorre semprlicemente utilizzare una stringa di lunghezza 1.
Per trovare la lunghezza di una stringa, accedere la sua proprietà length
:
> "hello".length 5
Ecco il nostro primo assaggio degli oggetti JavaScript! E' stato menzionato che le stringhe sono anch'esse oggetti? Ed hanno anche metodi:
> "hello".charAt(0) h > "hello, world".replace("hello", "goodbye") goodbye, world > "hello".toUpperCase() HELLO
Altri tipi
JavaScript distingue tra null
, che è un oggetto di tipo 'object' che indica un mancanza deliberata di valore, e undefined
, che è un oggetto di tipo 'undefined' che indica un valore non inizializzato — che significa, un valore che non è ancora stato assegnato. Parleremo delle variabili più avanti, ma in JavaScript è possibile dichiarare una variabile senza assegnarle un valore. Facendo questo, il tipo della variabile sarà undefined
.
JavaScript ha il tipo booleano, con possibili valori true
(vero) e false
(falso) (entrambi i quali sono parole chiave). Qualunque valore può essere convertito in booleano seguendo le seguenti regole:
false
,0
, la stringa vuota (""
),NaN
,null
, eundefined
diventano tuttifalse
- tutti gli altri valori diventano
true
E' possibile eseguire questa conversione esplicitamente usando la funzione Boolean()
:
> Boolean("") false > Boolean(234) true
Tuttavia, questo raramente è necessario, JavaScript eseguirà silenziosamente la conversione quando si aspetta un booleano, così come in una istruzione if
(vedi sotto). Per questa ragione, a volte si parla semplicemente di "valori veri" e "valori falsi" valori significativi che diventano true
e false
, rispettivamente, quando convertiti in booleani. In alternativa, tali valori possono essere chiamati "truthy" e "falsy", rispettivamente.
Le operazioni booleane come &&
(and logico), ||
(or logico), e !
(not logico) sono supportate; vedi sotto.
Variabili
Le nuove varibili sono dichiarate in JavaScript utilizzando la parola chiave var
:
var a; var name = "simon";
Se la variabile viene dichiarata senza assegnarle un valore, il suo tipo sarà undefined
.
Una differenza importante rispetto ad altri linguaggi come Java è che in JavaScript, i blocchi non hanno ambito; solo le funzioni hanno ambito. Quindi se una variabile viene definita utilizzando var
in una istruzione composta (ad esempio all'interno di una struttura di controllo if
), essa sarà visibile da parte dell'intera funzione.
Operatori
Gli operatori numerici in JavaScript sono +
, -
, *
, /
e %
- che è l'operatore per il resto. I valori sono assegnanti usando =
, e vi sono anche le istruzioni di assegnamento composte tipo +=
e -=
. Questi comprimono la forma x = x operatore y
.
x += 5 x = x + 5
E' possibile utilizzare ++
e --
per incrementare e decrementare rispettivamente. Questi possono essere usati come operatori prefissi o postfissi.
L'operatore + compie anche la concatenatione di stringhe:
> "hello" + " world" hello world
Se si somma una stringa ad un numero (o un altro valore) tutto viene convertito dalla prima stringa. Questo esempio potrebbe aiutare a chiarire il tutto:
> "3" + 4 + 5 345 > 3 + 4 + "5" 75
Sommare una stringa vuota ad un altro tipo è un utile maniera per convertirlo.
I confronti in JavaScript possono essere eseguiti usando <
, >
, <=
e >=
. Essi funzionano sia per le stringhe che per i numeri. L'uguaglianza è un pochino meno lineare. L'operatore di doppio uguale esegue la coercizione di tipo se viene eseguita tra tipi differenti, con a volte risultati interessanti:
> "dog" == "dog" true > 1 == true true
Per evitare la coercizione di tipo, si utilizza l'operatore triplo uguale:
> 1 === true false > true === true true
Vi sono anche gli operatori !=
e !==
.
JavaScript ha inoltre le opertazioni bit per bit. Se si desidera utilizzarle, sono lì.
Strutture di controllo
JavaScript ha una serie di strutture di controllo simili agli altri linguaggi della famiglia del C. Le istruzioni condizionali sono supportate da if
e else
(se e altrimenti) che possono essere concatenati insieme se desiderato:
var name = "kittens"; if (name == "puppies") { name += "!"; } else if (name == "kittens") { name += "!!"; } else { name = "!" + name; } name == "kittens!!"
JavaScript ha il ciclo while
ed il ciclo do-while
. Il primo è utile per un ciclo basico; il secondo per i cicli che si vuole essere sicuri che vengano eseguito almeno una volta:
while (true) { // an infinite loop! } var input; do { input = get_input(); } while (inputIsNotValid(input))
Il ciclo for
in JavaScript è lo stesso che in C e Java: esso permette di fornire le informazioni di controllo per il ciclo in una linea singola.
for (var i = 0; i < 5; i++) { // Will execute 5 times }
Gli operatori &&
e ||
usano un corto-circuito logico, che significa che quando sono eseguito il secondo operando è dipendente dal primo. Questo è utile per verificare oggetti nulli prima di accedere i lori attributi:
var name = o && o.getName();
Oppure per impostare valori di default:
var name = otherName || "default";
JavaScript ha un operatore ternario per espressioni condizionali:
var allowed = (age > 18) ? "yes" : "no";
L'istruzione switch può essere utilizzata per più diramazioni sulla base di un numero o una stringa:
switch(action) { case 'draw': drawit(); break; case 'eat': eatit(); break; default: donothing(); }
Se non viene inserita l'istruzione break
, l'esecuzione "naufragherà" nel prossimo livello. Questo è raramente il risultato voluto — in realtà vale la pena in particolare inserire un etichettatura deliberatamente con un commento, se vi vuole aiutare il debug:
switch(a) { case 1: // fallthrough case 2: eatit(); break; default: donothing(); }
La clausula default è opzionale. Si possono avere espressioni sia nello switch sia che nel case se si vuole; You can have expressions in both the switch part and the cases if you like; i confronti avvengono tra i due con l'operatore ===:
switch(1 + 3) { case 2 + 2: yay(); break; default: neverhappens(); }
Oggetti
Gli oggetti JavaScript sono semplicemente collezioni di coppie nome-valore. Come tali, essi sono simili a:
- Dizionari in Python
- Hashes in Perl e Ruby
- Hash tables in C e C++
- HashMaps in Java
- Array associativi in PHP
Il fatto che questa struttura dati è così diffusa è la prova della sua versatilità. Dal momento che tutto (barra i tipi base) in JavaScript è un oggetto, qualunque programma JavaScript implica naturalmente un grande ricorso alla ricerca nelle tabelle hash. E' buona cosa che siano così veloci!
La parte "name" è una stringa JavaScript, mentre il valore può essere qualunque valore JavaScript — incluso più oggetti. Questo permette di costruire strutture dati di complessità arbitraria.
Ci sono due modalità di base per creare un oggetto vuoto:
var obj = new Object();
E:
var obj = {};
Entrambe sono semanticamente equivalenti; la seconda è chiamata sintassi letterale dell'oggetto, ed è più convenienete. Questa sintassi è anche la base del formato JSON e dovrebbe essere preferita ogni volta.
Una volta creato, le proprietà di un oggetto possono essere nuovamente accedute in una o due modalità:
obj.name = "Simon"; var name = obj.name;
E...
obj["name"] = "Simon"; var name = obj["name"];
Anche queste sono semanticamente equivalenti. Il secondo metodo ha il vantaggio che il nome della proprietà viene fornito come stringa, che significa che può essere calcolato durante l'esecuzione e l'utilizzo di questo metodo evita che siano applicate ottimizzazioni del motore JavaScript e minifier. Può essere inoltre usato per impostare o ottenere proprietà con nomi che sono parole riservate:
obj.for = "Simon"; // Syntax error, because 'for' is a reserved word obj["for"] = "Simon"; // works fine
La sintassi dell'oggetto letterale può essere usata per inizializzare un oggetto nella sua interezza:
var obj = { name: "Carrot", "for": "Max", details: { color: "orange", size: 12 } }
Attributi di accesso possono essere concatenati:
> obj.details.color orange > obj["details"]["size"] 12
Array (matrici)
Gli array in JavaScript sono un tipo speciale di oggetto. Essi funzionano in modo molto simile agli oggetti regolari (le proprietà numeriche possono naturalmente essere accedute solo usando la sintassi []) ma hanno una proprietà magica chiamata 'length
'. Questa è sempre uno in più dell'indice massimo dell'array.
Il vecchio metodo per creare un array è il seguente:
> var a = new Array(); > a[0] = "dog"; > a[1] = "cat"; > a[2] = "hen"; > a.length 3
Una notazione più conveniente è l'utilizzo di una array letterale:
> var a = ["dog", "cat", "hen"]; > a.length 3
Lasciare una virgola finale al termine di un array letterale è incompatibile tra i browser, quindi non fatelo.
Nota che array.length
non è necessariamente il numero di elementi nell'array. Considera il seguente esempio:
> var a = ["dog", "cat", "hen"]; > a[100] = "fox"; > a.length 101
Ricorda — la lunghezza dell'array è uno più dell'indice più alto.
Se si interroga un indice dell'array inesistente, la risposta sarà undefined
:
> typeof a[90] undefined
Se si prende in considerazione quanto sopra, è possibile scorrere un array utilizzando le istruzioni seguenti:
for (var i = 0; i < a.length; i++) { // Do something with a[i] }
Questo è un po' inefficiente, poichè si ricerca la proprietà length una volta ogni ciclo. Un possibile miglioramento è questo:
for (var i = 0, len = a.length; i < len; i++) { // Do something with a[i] }
Un modo ancora più simpatico è questo:
for (var i = 0, item; item = a[i++];) { // Do something with item }
Qui si stanno impostando due variabili. L'assegnamento nella parte centrale del ciclo for
è anche verificato per veridicità — se ha successo, il ciclo continua. Siccome i
viene incrementato ogni volta, gli elementi dalla matrice saranno assegnati all'elemento in ordine sequenziale. Il ciclo termina quando viene trovato un elemento "falso" (come un undefined
).
Nota che questo trucco dovrebbe essere usato solo per gli array che sappiamo non contengano valori "falsi" (array di oggetti o nodi del DOM per esempio). Se si effettua l'iterazione dei dati numerici che potrebbero includere uno 0, o dati stringa che potrebbero includere la stringa vuota, è necessario utilizza l'idioma i, len
in sostituzione.
Un altro modo per iterare è di utilizzare il ciclo for...in
. Nota che se viengono aggiunte nuove proprietà all' Array.prototype
, saranno anch'esse iterate da questo ciclo:
for (var i in a) { // Do something with a[i] }
Se si vuole accodare un elemento all'array, la maniera più sicura per farlo è questa:
a[a.length] = item; // same as a.push(item);
Poichè a.length
è uno in più dell'indice più alto, si può essere sicuri di stare assegnando ad una posizione vuota alla fine dell'array.
Gli arrays nascono con alcuni metodi:
Method name | Description |
---|---|
a.toString() |
|
a.toLocaleString() |
|
a.concat(item[, itemN]) |
Ritorna un nuovo array con gli elementi aggiunti ad esso. |
a.join(sep) |
|
a.pop() |
Rimuove e restituisce l'ultimo elemento. |
a.push(item[, itemN]) |
Push aggiunge uno o più elementi alla fine. |
a.reverse() |
|
a.shift() |
|
a.slice(start, end) |
Ritorna un sub-array. |
a.sort([cmpfn]) |
Riceve una funzione di comparazione opzionale. |
a.splice(start, delcount[, itemN]) |
Permette di modificare un array cancellando una sezione e sostituendola con più elementi. |
a.unshift([item]) |
Antepone elementi all'inizio dell'array |
Funzioni
Insieme con gli oggetti, le funzioni sono la componente principale nella comprensione di JavaScript. La funzione più elementare non potrebbe essere molto più semplice:
function add(x, y) { var total = x + y; return total; }
Ciò dimostra tutto quello che c'è da sapere sulle funzioni di base. Una funzione JavaScript può ricevere 0 o più parametri. Il corpo della funzione può contenere tutte le istruzioni che si desidera, e può dichiarare le proprie variabili che saranno locali alla stessa. L'istruzione return
può essere usata per restituire un valore in qualsiasi momento, terminando la funzione. Se non viene utilizzata l'istruzione return (oppure viene ritornato un valore vuoto o indefinito), JavaScript restituisce undefined
.
I parametri denominati risultano più simili alle linee guida di ogni altra cosa. È possibile chiamare una funzione senza passare i parametri che si aspetta, nel qual caso saranno impostati su undefined
.
> add() NaN // You can't perform addition on undefined
È anche possibile passare più argomenti di quelli che la funzione si aspetta:
> add(2, 3, 4) 5 // added the first two; 4 was ignored
Questo può sembrare un po' sciocco, ma le funzioni hanno accesso a una variabile aggiuntiva all'interno del loro corpo chiamata argomenti
, che è un oggetto simil array che detiene tutti i valori passati alla funzione. Riscriviamo la funzione somma per ricevere tutti valori che vogliamo:
function add() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum; } > add(2, 3, 4, 5) 14
Questo, però, non è realmente più utile che scrivere 2 + 3 + 4 + 5
. Creiamo una funzione per il calcolo della media:
function avg() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum / arguments.length; } > avg(2, 3, 4, 5) 3.5
Questa è piuttosto utile, ma introduce un nuovo problema. La funzione avg()
riceve una lista di argomenti separati da una virgola — ma cosa succede se si vuole trovare la media di un array? Si potrebbe semplicemente riscrivere la funzione come segue:
function avgArray(arr) { var sum = 0; for (var i = 0, j = arr.length; i < j; i++) { sum += arr[i]; } return sum / arr.length; } > avgArray([2, 3, 4, 5]) 3.5
Ma sarebbe bello essere in grado di riutilizzare la funzione che abbiamo già creato. Fortunatamente, JavaScript permette di chiamare una funzione e di chiamarla con un array arbitrario di argomenti, usando il metodo apply()
di qualunque oggetto di funzione.
> avg.apply(null, [2, 3, 4, 5]) 3.5 is the array to use as arguments; the first will be discussed later on. This emphasizes the fact that functions are objects too.
JavaScript permette la crazione di funzioni anomime.
var avg = function() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum / arguments.length; }
Questa è semanticamente equivalente alla forma function avg()
. Essa è estremamente potente, in quando consente di definire una funzione ovunque si possa normalmente inserire un espressione. Questo consente ogni sorta di trucco intelligente. Ecco un modo di "nascondere" alcune variabili locali - come in un ambito di blocco in C:
> var a = 1; > var b = 2; > (function() { var b = 3; a += b; })(); > a 4 > b 2
JavaScript consente di chiamare le funzioni in modo ricorsivo. Ciò è particolarmente utile per affrontare le strutture ad albero, come ad esempio nel DOM del browser.
function countChars(elm) { if (elm.nodeType == 3) { // TEXT_NODE return elm.nodeValue.length; } var count = 0; for (var i = 0, child; child = elm.childNodes[i]; i++) { count += countChars(child); } return count; }
Questo mette in evidenza un potenziale problema con le funzioni anonime: come fare a richiamarle ricorsivamente se non hanno un nome? ow do you call them recursively if they don't have a name? La risposta si trova con l'oggetto . L'uso della arguments
, che oltre ad agire come una lista di argomenti, fornisce anche una propietà chiamata arguments.callee
arguments.callee
è deprecato e anche disabilitato nel modo strict (rigosroso). In sostituzione si devono utilizzare le "funzioni anonime nominate" come di seguito:
var charsInBody = (function counter(elm) { if (elm.nodeType == 3) { // TEXT_NODE return elm.nodeValue.length; } var count = 0; for (var i = 0, child; child = elm.childNodes[i]; i++) { count += counter(child); } return count; })(document.body);
Il nome fornito a una funzione anonima come sopra è (o almeno dovrebbe essere) disponibile solo nell'ambito stesso della funzione. Questo consente sia più ottimizzazioni da svolgere da parte del motore sia un codice più leggibile.
Oggetti personalizzati
Nella programmazione Object Oriented classica, gli oggetti sono collezioni di dati e metodi che operano su quei dati. JavaScript è un linguaggio basato su prototipi che non contiene l'istruzione classe, così come si trova in C++ o Java. (Questo a volte è fonte di confusione per i programmatori abituati ai linguaggi con una dichiarazione di classe.) Al suo posto, JavaScript usa le funzioni come classi. Consideriamo un oggetto persona con i campi nome e cognome. Ci sono due modi di visualizzare il nominativo: come "nome cognome" o come "cognome, nome". Usando le funzioni e gli oggetti che abbiamo visto in precedenza, ecco un modo di ottenere il risultato voluto:
function makePerson(first, last) { return { first: first, last: last } } function personFullName(person) { return person.first + ' ' + person.last; } function personFullNameReversed(person) { return person.last + ', ' + person.first } > s = makePerson("Simon", "Willison"); > personFullName(s) Simon Willison > personFullNameReversed(s) Willison, Simon
Questo funziona, ma è piuttosto brutto. Si finisce con dozzine di funzioni nel namespace globale. Ciò di cui abbiamo veramente bisogno è un modo per associare una funzione ad un oggetto. Dal momento che le funzioni sono oggetti, questo è facile:
function makePerson(first, last) { return { first: first, last: last, fullName: function() { return this.first + ' ' + this.last; }, fullNameReversed: function() { return this.last + ', ' + this.first; } } } > s = makePerson("Simon", "Willison") > s.fullName() Simon Willison > s.fullNameReversed() Willison, Simon
Vi è qualcosa che non abbiamo visto prima: la parola chiave 'this
'. Usata dento una funzione, 'this
' si riferisce all'oggetto corrente. Che cosa significa in realtà è specificato dal modo in cui è stato chiamata tale funzione. Se è stata chiamata usando la notazione col punto o la notazione con le parentesi su un oggetto, questo oggetto diventa 'this
'. Se la notazione col punto non è stata usata per la chiamata, 'this
' si riferisce all'oggetto globale. Questa è una causa di errori frequente. Ad esempio:
> s = makePerson("Simon", "Willison") > var fullName = s.fullName; > fullName() undefined undefined
Quando chiamiamo fullName()
, 'this
' è legata all'oggetto globale. Dato che non ci sono variabili globali chiamate first
o last
riceviamo undefined
per ognuna delle due.
Possiamo prendere il vantaggio della parola chiave 'this
' per migliorare la nostra funzione makePerson
:
function Person(first, last) { this.first = first; this.last = last; this.fullName = function() { return this.first + ' ' + this.last; } this.fullNameReversed = function() { return this.last + ', ' + this.first; } } var s = new Person("Simon", "Willison");
Abbiamo introdotto un'altra parola chiave: 'new
'. new
è fortemente correlata a 'this
'. Quello che fa è creare un oggetto vuoto nuovo di zecca, e quindi chiamare la funzione specificata, con 'this
' impostato sul nuovo oggetto. Le funzioni che sono disegnate per essere richiamate dalla 'new
' sono chiamate costruttori. La pratica comune è di nominare queste funzioni con la prima lettera maiuscola in modo da ricorsarsi di chiamarle con il new
.
I nostri oggetti persona stanno migliorando, ma vi sono ancora alcuni lati brutti in loro. Ogni volta che si crea un oggetto persona, stiamo creando due nuovi oggetti funzione all'interno di esso - non sarebbe meglio se il codice fosse stato condiviso?
function personFullName() { return this.first + ' ' + this.last; } function personFullNameReversed() { return this.last + ', ' + this.first; } function Person(first, last) { this.first = first; this.last = last; this.fullName = personFullName; this.fullNameReversed = personFullNameReversed; }
Così va meglio: stiamo creando i metodi della funzione una sola volta, e assegnando ad essi i riferimenti all'interno del costruttore. Possiamo fare di meglio? La risposta è sì:
function Person(first, last) { this.first = first; this.last = last; } Person.prototype.fullName = function() { return this.first + ' ' + this.last; } Person.prototype.fullNameReversed = function() { return this.last + ', ' + this.first; }
Person.prototype
è un oggetto condiviso da tutte le istanze di Person
. Essa fa parte di una catena di ricerca (che ha un nome speciale, "catena di prototipi"): ogni volta che si tenta di accedere ad una proprietà di Person
che non è impostata, JavaScript controllerà Person.prototype
per vedere se quella proprietà esiste al suo interno. Come risultato, qualsiasi valore assegnato a Person.prototype
diventa disponibile a tutte le istanze del costruttore per mezzo dell'oggetto this
.
Si tratta di uno strumento incredibilmente potente. JavaScript consente di modificare il prototipo di qualcosa in qualsiasi momento nel programma, il che significa che è possibile aggiungere metodi extra per gli oggetti esistenti in fase di esecuzione:
> s = new Person("Simon", "Willison"); > s.firstNameCaps(); TypeError on line 1: s.firstNameCaps is not a function > Person.prototype.firstNameCaps = function() { return this.first.toUpperCase() } > s.firstNameCaps() SIMON
È interessante notare che è anche possibile aggiungere le cose al prototipo degli oggetti nativi JavaScript. Aggiungiamo un metodo a String
che ritorni la stringa al contrario:
> var s = "Simon"; > s.reversed() TypeError on line 1: s.reversed is not a function > String.prototype.reversed = function() { var r = ""; for (var i = this.length - 1; i >= 0; i--) { r += this[i]; } return r; } > s.reversed() nomiS
Il nostro nuovo metodo funziona anche con le stringhe letterali!
> "This can now be reversed".reversed() desrever eb won nac sihT
Come detto prima, il prototipo fa parte di una catena. La radice di questa catena è Object.prototype
, i cui metodi includono toString()
— è questo metodo che viene chiamato quando si tenta di rappresentare un oggetto come una stringa. Questo è utile per verificare il nostro oggetto Person
:
> var s = new Person("Simon", "Willison"); > s [object Object] > Person.prototype.toString = function() { return '<Person: ' + this.fullName() + '>'; } > s <Person: Simon Willison>
Ricordate come avg.apply()
aveva un primo argomento nullo? Possiamo riesaminarlo adesso. Il primo argomento di apply()
è l'oggetto che dovrebbe essere trattato come 'this
'. Per esempio, qui una semplice implementazione di 'new
':
function trivialNew(constructor) { var o = {}; // Create an object constructor.apply(o, arguments); return o; }
Questa non è un'esatta replica di new
in quanto non imposta la catena del prototipo (sarebbe difficile da illustrare). Non è una cosa che si usa molto spesso, ma è utile conoscerla. In questo snippet, ...args
(puntini inclusi) è chiamato il "rest arguments" – come indicato dal nome, e contiene il resto degli argomenti. Per ora, questa "feature" è sperimentale e solo disponibile in Firefox; è raccomandato attaccare gli arguments
per ora.
Chiamare
var bill = trivialNew(Person, "William", "Orange");
è dunque quasi equivalente a
var bill = new Person("William", "Orange");
apply()
ha una funzione sorella dal nome call
, che di nuovo ti consente di impostare 'this
' ma prende una lista espansa di argomenti invece che un array.
function lastNameCaps() { return this.last.toUpperCase(); } var s = new Person("Simon", "Willison"); lastNameCaps.call(s); // Is the same as: s.lastNameCaps = lastNameCaps; s.lastNameCaps();
Inner functions
In JavaScript è consentito dichiarare una funzione all'interno di un'altra funzione. Lo abbiamo già visto prima, nel caso della precedente funzione makePerson()
. Un dettaglio importante di funzioni innestate in JavaScript è che esse possono accedere alle variabili della funzione di livello superiore:
function betterExampleNeeded() { var a = 1; function oneMoreThanA() { return a + 1; } return oneMoreThanA(); }
Ciò è di grande utilità per scrivere codice più manutenibile. Se una funzione dipende da una o due altre funzioni che non sono usate in nessuna altra parte del tuo codice, è possibile nidificare quelle funzioni di utilità dentro la funzione che sarà chiamata dall'esterno. Questo riduce il numero di funzioni che si trovano nel "global scope", che è sempre una buona cosa.
Questa è anche una grande cosa contro il richiamo di variabili globali. Quando si scrive codice complesso si è spesso tentati di usare variabili globali per condividere i valori tra più funzioni — e ciò rende il codice difficile da manutenere. Le funzioni nidificate possono condividere le variabili della funzione padre, così è possibile usare questo meccanismo per accoppiare le funzioni quando serve, senza contaminare il tuo namespace globale — rendi locali le variabili globali per piacere. Questa tecnica dovrebbe essere usata con parsimonia, ma è una capacità utile da avere.
Closures
Questo ci porta ad una delle più potenti astrazioni che JavaScript ha da offrire — ma anche quella che può generare più confusione. Cosa fa questo codice sotto?
function makeAdder(a) { return function(b) { return a + b; } } x = makeAdder(5); y = makeAdder(20); x(6) ? y(7) ?
Il nome della funzione makeAdder
dovrebbe essere esplicito: essa può creare delle nuove funzioni 'adder', che, se chiamate con un determinato argomento, lo addizionano all'argomento con il quale sono state create.
Quello che sta avvenendo qui è praticamente la stessa cosa vista precedentemente con le "inner functions": una funzione definita dentro un'altra funzione ha accesso alle variabili della funzione esterna. La sola differenza è che in questo caso la funzione esterna ha già restituito il suo risultato, e quindi il senso comune sembrerebbe indicare che le sue variabili locali non siano più disponibili. Ma esse esistono ancora — altrimenti le funzioni "adder" non sarebbero capaci di lavorare. Quello che c'è di più è che ci sono due "copie" differenti delle variabili locali di makeAdder
— una nella quale a
è 5 e una nella quale a
è 20. Così il risultato di quelle chiamate di funzione è il seguente:
x(6) // returns 11 y(7) // returns 27
Ecco cosa sta effettivamente avvenendo. Quando JavaScript esegue una funzione, viene creato un oggetto con il proprio ambito di visibilità ('scope object') per trattenere le variabili locali di quella funzione. Esso è inizializzato con tutte le variabili passate in ingresso alla funzione come parametri. Ciò è simile all'oggetto globale in cui si trovano tutte le variabili globali e le funzioni, ma con una coppia di differenze importanti: primo, un nuovo 'scope object' etichettato è creato ogni volta che una funzione inizia l'esecuzione, e secondo, a differenza dell'oggetto globale (che nei bowser è accessibile come 'window') non si può accedere direttamente a questi 'scope object' dal tuo codice JavaScript. Ad esempio non c'è nessun meccanismo per iterare sulle proprietà dello 'scope object' corrente.
So when makeAdder
is called, a scope object is created with one property: a
, which is the argument passed to the makeAdder
function. makeAdder
then returns a newly created function. Normally JavaScript's garbage collector would clean up the scope object created for makeAdder
at this point, but the returned function maintains a reference back to that scope object. As a result, the scope object will not be garbage collected until there are no more references to the function object that makeAdder
returned.
Scope objects form a chain called the scope chain, similar to the prototype chain used by JavaScript's object system.
A closure is the combination of a function and the scope object in which it was created.
Closures let you save state — as such, they can often be used in place of objects.
Memory leaks
An unfortunate side effect of closures is that they make it trivially easy to leak memory in Internet Explorer. JavaScript is a garbage collected language — objects are allocated memory upon their creation and that memory is reclaimed by the browser when no references to an object remain. Objects provided by the host environment are handled by that environment.
Browser hosts need to manage a large number of objects representing the HTML page being presented — the objects of the DOM. It is up to the browser to manage the allocation and recovery of these.
Internet Explorer uses its own garbage collection scheme for this, separate from the mechanism used by JavaScript. It is the interaction between the two that can cause memory leaks.
A memory leak in IE occurs any time a circular reference is formed between a JavaScript object and a native object. Consider the following:
function leakMemory() { var el = document.getElementById('el'); var o = { 'el': el }; el.o = o; }
The circular reference formed above creates a memory leak; IE will not free the memory used by el
and o
until the browser is completely restarted.
The above case is likely to go unnoticed; memory leaks only become a real concern in long running applications or applications that leak large amounts of memory due to large data structures or leak patterns within loops.
Leaks are rarely this obvious — often the leaked data structure can have many layers of references, obscuring the circular reference.
Closures make it easy to create a memory leak without meaning to. Consider this:
function addHandler() { var el = document.getElementById('el'); el.onclick = function() { this.style.backgroundColor = 'red'; } }
The above code sets up the element to turn red when it is clicked. It also creates a memory leak. Why? Because the reference to el
is inadvertently caught in the closure created for the anonymous inner function. This creates a circular reference between a JavaScript object (the function) and a native object (el
).
needsTechnicalReview();
There are a number of workarounds for this problem. The simplest is not to use the el
variable:
function addHandler(){ document.getElementById('el').onclick = function(){ this.style.backgroundColor = 'red'; } }
Surprisingly, one trick for breaking circular references introduced by a closure is to add another closure:
function addHandler() { var clickHandler = function() { this.style.backgroundColor = 'red'; }; (function() { var el = document.getElementById('el'); el.onclick = clickHandler; })(); }
The inner function is executed straight away, and hides its contents from the closure created with clickHandler
.
Another good trick for avoiding closures is breaking circular references during the window.onunload
event. Many event libraries will do this for you. Note that doing so disables bfcache in Firefox 1.5, so you should not register an unload
listener in Firefox, unless you have other reasons to do so.
Original Document Information
- Author: Simon Willison
- Last Updated Date: March 7, 2006
- Copyright: © 2006 Simon Willison, contributed under the Creative Commons: Attribute-Sharealike 2.0 license.
- More information: For more information about this tutorial (and for links to the original talk's slides), see Simon's Etech weblog post.