--- title: Una reintroduzione al Java Script (Tutorial JS) slug: Web/JavaScript/A_re-introduction_to_JavaScript translation_of: Web/JavaScript/A_re-introduction_to_JavaScript original_slug: Web/JavaScript/Una_reintroduzione_al_JavaScript ---
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. JavaScript viene attualmente utilizzato da un numero incredibile di applicazioni di alto profilo, che mostrano come la conoscenza profonda di questa tecnologia sia un importante abilità per qualunque sviluppatore web.
È utile iniziare con un accenno alla storia del linguaggio. JavaScript fu creato nel 1995 da Brendan Eich, mentre lavorava come ingegnere presso Netscape, e rilasciata per la prima volta con Netscape 2 all'inizio del 1996. Originariamente doveva essere chiamato LiveScript, ma fu poi rinominato in Javacript 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.
Diversi mesi dopo, Microsoft rilasciò JScript con Internet Explorer 3, un linguaggio che lavorava in modo simile a JavaScript ed era quasi del tutto compatibile con esso. Netscape sottomise il linguaggio alla Ecma International, un'organizzazione europea di standardizzazione, che porta alla nascita della prima edizione degli standard ECMAScript. Lo standard ricevette un significativo aggiornamento come ECMAScript edition 3 nel 1999, ed è rimasto più o meno stabile da allora. La quarta edizione fu abbandonata a causa delle differenze di vedute sulla complessità del linguaggio. Molte parti della quarta edizione formano la base del nuovo ECMAScript edizione 5, pubblicato nel dicembre del 2009, e per la sesta edizione pubblicata a giugno 2015.
Diversamente dalla maggior parte dei linguaggi di programmazione, il linguaggio JavaScript non ha il concetto di input e output. È stato concepito per essere eseguito come linguaggio di scripting in un ambiente ospite, ed è responsabilità dell'ambiente ospite fornire meccanismi per comunicare con il mondo esterno. L'ambiente ospite più comune è il browser, ma interpreti JavaScript possono essere trovati anche in Adobe Acrobat, Photoshop, motore Widget di Yahoo! , e addirittura in ambienti lato server o in desktop environment come GNOME (una delle GUI più popolari per i sistemi GNU/Linux) e altri ancora.
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 utilizzate 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:
E ci sono anche alcuni tipi nativi di Errori. Comunque, le cose sono molto più semplici se ci atteniamo al primo diagramma
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 conversione 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 stringa 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 restituita se la stringa non è numerica:
> parseInt("hello", 10) NaN
Il NaN
è tossico: se viene fornito come input 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 è non è valido per il formato numerico specificato, quindi restituiscono 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 metodo da soli utilizzando la console e sarà possibile capire meglio le differenze.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 semplicemente 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
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 — ossia un valore che non è stato ancora assegnato. Parleremo delle variabili più avanti, ma in JavaScript è possibile dichiarare una variabile senza assegnarle un valore. Facendo questo, il tipo della variabile creata 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
, e undefined
diventano tutti false
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.
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.
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 concatenazione di stringhe:
> "hello" + " world" hello world
Se si somma una stringa ad un numero (o ad 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 operazioni bit per bit. Se si desidera utilizzarle, sono lì.
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 eseguiti 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 &&
(and logico) e ||
(or logico) usano un corto-circuito logico, questo significa che quando vengono eseguiti il secondo operando è dipendente dal primo. Questo è utile per verificare oggetti nulli prima di accedere ai loro 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; i confronti avvengono tra i due con l'operatore ===:
switch(1 + 3) { case 2 + 2: yay(); break; default: neverhappens(); }
Gli oggetti JavaScript sono semplicemente collezioni di coppie nome-valore. Come tali, essi sono simili a:
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ù conveniente. Questa sintassi è anche la base del formato JSON e dovrebbe essere preferita ogni volta.
Una volta creato si può accedere alle proprietà di un oggetto 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
Gli array in JavaScript sono un tipo speciale di oggetto. Essi funzionano in modo molto simile agli oggetti regolari (si può accedere alle proprietà numeriche 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 vengono 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 che l'elemento sarà assegnato 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]) |
Restituisce 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) |
Restituisce 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 |
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 (zero) 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 restituito 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 i 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 creazione di funzioni anonime.
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? 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 (rigoroso). 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.
Nella programmazione Object Oriented classica, gli oggetti sono collezioni di dati e metodi che operano su quei dati. JavaScript è un linguaggio basato su prototipi e 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 dentro una funzione, 'this
' si riferisce all'oggetto corrente. Che cosa significa in realtà è specificato dal modo in cui è stata 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 ricordarsi 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 restituisca 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();
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.
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 paio 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.
Quindi, quando makeAdder
è chiamato viene creato un oggetto scope con una proprietà: a
, che è l'argomento passato alla funzione makeAdder
. A questo punto makeAdder
restituisce quindi una funzione appena creata. Normalmente il raccoglitore di rifiuti di JavaScript eliminerebbe l'oggetto scope creato per makeAdder
, ma la funzione restituita mantiene un riferimento a tale oggetto scope. Di conseguenza l'oggetto scope non verrà eliminato finchè non ci saranno più riferimenti all'oggetto che la funzione makeAdder
restituisce.
Gli oggetti scope formano una catena chiamata 'scope chain', simile alla catena di prototipi, 'prototype chain', del sistema a oggetti di JavaScript.
Una closure è la combinazione di una funzione e dell'oggetto scope in cui è stata creata.
Le closures ti consentono di salvare lo stato e in quanto tali, possono spesso essere utilizzate al posto degli oggetti. Puoi trovare alcune eccellenti introduzioni alle closures (in lingua inglese).
Uno sfortunato effetto collaterale delle chiusure è il rendere facile perdere memoria su Internet Explorer. JavaScript alloca gli oggetti nella memoria al momento della loro creazione e tale memoria viene recuperata dal browser quando non rimangono riferimenti a un oggetto. Gli oggetti forniti dall'host vengono gestiti da quell'ambiente. Gli host del browser devono gestire un gran numero di oggetti che rappresentano la pagina HTML presentata: gli oggetti del DOM. Spetta al browser gestire l'assegnazione e il recupero di questi. Internet Explorer utilizza il proprio schema di raccolta dei rifiuti per questo, separato dal meccanismo utilizzato da JavaScript. È l'interazione tra i due che può causare perdite di memoria. Una perdita di memoria in IE si verifica ogni volta che viene formato un riferimento circolare tra un oggetto JavaScript e un oggetto nativo. Considera quanto segue:
function leakMemory() { var el = document.getElementById('el'); var o = { 'el': el }; el.o = o; }
Il riferimento circolare formato sopra crea una perdita di memoria;
IE non libererà la memoria utilizzata da el fino al completo riavvio del browser. Il caso di cui sopra rischia di passare inosservato; le perdite di memoria diventano un problema reale solo nelle applicazioni a esecuzione prolungata o nelle applicazioni che perdono grandi quantità di memoria a causa di grandi strutture di dati o schemi di perdite all'interno dei loop. Le perdite sono raramente così ovvie: spesso la struttura dei dati trapelati può avere molti livelli di riferimenti, oscurando il riferimento circolare. Le chiusure rendono facile creare una perdita di memoria senza volerlo. Considera questo:
function addHandler() { var el = document.getElementById('el'); el.onclick = function() { this.style.backgroundColor = 'red'; } }
Il codice precedente imposta l'elemento in modo che diventi rosso quando viene cliccato. Crea anche una perdita di memoria. Perché? Perché il riferimento a el è inavvertitamente catturato nella chiusura creata per la funzione interna anonima. Questo crea un riferimento circolare tra un oggetto JavaScript (la funzione) e un oggetto nativo (el).
needsTechnicalReview();
Esistono numerose soluzioni per questo problema. Il più semplice è non usare la variabile el:
function addHandler(){ document.getElementById('el').onclick = function(){ this.style.backgroundColor = 'red'; } }
Sorprendentemente, un trucco per rompere i riferimenti circolari introdotti da una chiusura è aggiungere un'altra chiusura:
function addHandler() { var clickHandler = function() { this.style.backgroundColor = 'red'; }; (function() { var el = document.getElementById('el'); el.onclick = clickHandler; })(); }
La funzione interna viene eseguita immediatamente, e nasconde il suo contenuto dalla chiusura creata con clickHandler. Un altro buon trucco per evitare le chiusure è rompere i riferimenti circolari durante l'evento window.onunload. Molte librerie di eventi lo faranno per te. Nota che così facendo si disabilita bfcache in Firefox 1.5, quindi non dovresti registrare un listener di scaricamento in Firefox, a meno che tu non abbia altri motivi per farlo.