Abbiamo detto che le variabili JavaScript possono contenere valori di qualsiasi tipo e cambiare senza problemi il tipo di dato contenuto. Questa flessibilità libera lo sviluppatore dal dover dichiarare esplicitamente il tipo di dato che può contenere una variabile, scaricando però sull'engine JavaScript il compito di dover prendere delle decisioni in determinate situazioni ed effettuare delle conversioni implicite.
Consideriamo il seguente esempio:
var x = "12";
var y = "13";
var z = x * y;
Il valore che ci aspettiamo nella variabile z
è il risultato della moltiplicazione dei valori numerici risultanti dalla conversione delle stringhe contenute in x
e y
. Ed in effetti questo è il comportamento di JavaScript. Ma vediamo un altro esempio:
var x = "12";
var y = x + 13;
Che valore avrà la variabile y
? Sarà la stringa "1213"
o l'intero 25
?
Per complicare ancora un po' le cose, consideriamo un'altra situazione:
var x;
var y = x + 13;
Quale sarà il valore di y
in questo caso?
In assenza di indicazioni da parte dello sviluppatore, JavaScript dovrà prendere delle decisioni e convertire l'espressione in un tipo piuttosto che in un altro. Conoscere i criteri in base ai quali JavaScript converte implicitamente le espressioni è fondamentale per evitare bug spesso molto insidiosi.
Partiamo dal principio in base al quale in JavaScript ogni tipo di dato primitivo può essere convertito in un altro tipo di dato primitivo: numeri, stringhe, booleani, undefined
e null
.
Iniziamo col vedere come si comporta JavaScript quando deve convertire un valore in un booleano. La seguente tabella riepiloga le conversioni implicite previste.
Tipo | Valore booleano |
---|---|
undefined |
false |
null |
false |
numero | false se 0 o NaN ,true in tutti gli altri casi |
stringa | false se stringa vuota,true in tutti gli altri casi |
In base a questo schema, quindi, la seguente espressione restituirà la stringa "pari" o "dispari" in base alla conversione in booleano del resto della divisione per due della variabile x
:
x%2 ? "dispari" : "pari";
La conversione di un valore in numero segue le regole riassunte nella seguente tabella:
Tipo | Valore numerico |
---|---|
undefined |
NaN |
null |
0 |
booleano | 1 se true ,0 se false |
stringa | intero, decimale, zero o NaN in base alla specifica stringa |
Come possiamo vedere dalla tabella, la conversione implicita di una stringa in valore numerico non è così lineare come per gli altri tipi di dato.
Iniziamo col dire che una stringa che non rappresenta un numero viene convertita nel valore numerico NaN
, come nella seguente espressione:
"uno" * 2
La stringa vuota viene convertita in zero, mentre le stringhe che rappresentano numeri vengono convertite nei corrispondenti valori numerici interi o decimali, in base alla presenza o meno del punto decimale.
Per quanto riguarda la conversione implicita di un valore in stringa, la tabella di conversione è la seguente:
Tipo | Valore stringa |
---|---|
undefined |
"undefined" |
null |
"null" |
booleano | "true" se true "false" se false |
numero | "NaN" se NaN, "Infinity" se Infinity la stringa che rappresenta il numero negli altri casi |
Anche in questo caso la corrispondenza più complessa è quella con i valori numerici.
Le regole di conversione descritte sono facilmente applicabili quando siamo di fronte ad un operatore binario che opera su un solo tipo di dato. Ad esempio, la moltiplicazione opera soltanto sui numeri, per cui la conversione delle variabili coinvolte può avvenire soltanto verso i numeri, senza situazioni di ambiguità. Cioè, in presenza di un'espressione del genere:
x * y
JavaScript proverà a convertire entrambe le variabili in valori numerici.
Operatori polimorfi
Al contrario, di fronte ad operatori polimorfi, cioè operatori che prevedono operandi di tipo diverso, la situazione è un po' più complessa. Esempi di operatori polimorfi sono la somma e la concatenazione di stringhe, dato che utilizzano lo stesso simbolo +
, oppure gli operatori relazionali.
In presenza di espressioni come quelle che abbiamo evidenziato all'inizio di questa sezione e che riproponiamo di seguito, l'engine JavaScript si trova a dover fare una scelta ben precisa per stabilire verso quale tipo di dato convertire gli operandi.
var x = "12";
var y = x + 13;
Le regole seguite dall'engine JavaScript variano in base agli specifici operatori. Nel caso dell'operatore + la regola stabilisce che: se almeno uno dei due operandi è una stringa, allora viene effettuata una concatenazione di stringhe, altrimenti viene eseguita una addizione.
Alla luce di questa regola possiamo sciogliere il dubbio sull'espressione precedente. Dato che la variabile x
contiene una stringa, l'operatore +
sarà interpretato come una concatenazione di stringhe e pertanto il valore 13 sarà convertito nella stringa "13"
. Il valore di y
sarà quindi la stringa "1213"
. Da notare che la regola non cita gli altri tipi di dato di JavaScript. Questo vuol dire che in presenza di espressioni un po' strane come la seguente:
true + null
dal momento che non è presente nessuna stringa, l'operatore +
viene interpretato come addizione e quindi i valori true e null vengono convertiti in valori numerici, con il risultato finale di 1
.
Un altro caso molto interessante è quello dell'uso degli operatori logici come operatori polimorfi. Un tipico esempio è quello dell'operatore logico ||
(OR) spesso utilizzato per impostare una variabile ad un valore predefinito, come nei seguenti esempi:
var x = y || "";
var z = h || 1;
In questo caso le espressioni assegnate alle variabili x
e z
sono il risultato della particolare combinazione di una variabile e di una stringa, nel primo caso, e di un numero, nel secondo caso. La regola generale, in questa situazione, prevede che venga restituito il primo valore se esso è convertibile in una espressione vera, altrimenti viene restituito il secondo valore.
Nel primo caso, quindi, se la variabile y
contiene un valore non nullo (es.: una stringa non vuota) viene assegnato a x
il valore della variabile y
, altrimenti viene assegnato il valore ""
. Analogamente, nel secondo caso, viene assegnato alla variabile z
il valore 1
solo se h ha un valore nullo.
Facciamo presente che non necessariamente h
deve avere un valore numerico e y
un valore di tipo stringa.
In presenza degli operatori relazionali >
, >=
, <
e <=
si applica la seguente regola: se nessuno dei due operandi è un numero, allora viene eseguito un confronto tra stringhe, altrimenti viene eseguito un confronto tra numeri.
Questo vuol dire che analizzando la seguente espressione:
"12" > 10
JavaScript converte in numero la stringa "12" ed esegue un confronto tra numeri. Mentre di fronte al seguente confronto:
true > null
non essendo coinvolti numeri il confronto è equivalente a quello della seguente espressione:
"true" > "null"
Per quanto riguarda gli operatori di confronto ==
e !=
viene presa in considerazione la seguente regola: se entrambi gli operatori sono stringhe allora viene effettuato un confronto tra stringhe, altrimenti si esegue un confronto tra numeri; unica eccezione è null == undefined
che è vera per definizione
In altre parole, a parte il confronto tra stringhe e l'eccezione specificata, in tutti gli altri casi gli operandi vengono convertiti in valori numerici.
Uguaglianza (e disuguaglianza) stretta
Un discorso a parte vale per gli operatori di uguaglianza e disuguaglianza stretta (===
e !==
). Questi operatori confrontano gli operandi senza effettuare alcuna conversione. Quindi due espressioni vengono considerate uguali soltanto se sono dello stesso tipo ed rappresentano effettivamente lo stesso valore.
Evitare le conversioni implicite
Le conversioni implicite di JavaScript sono spesso fonte di bug, in particolar modo quando abbiamo a che fare con espressioni complesse. Il modo migliore per non incappare in questi potenziali bug è evitare le conversioni implicite. Ad esempio, è preferibile confrontare espressioni e valori dello stesso tipo ed utilizzare sempre gli operatori di uguaglianza e disuguaglianza stretta invece di quelli ordinari, in modo da essere sicuri di quello che si sta confrontando.
parseInt e parseFloat
Se non si può evitare il confronto tra tipi diversi, è opportuno convertire esplicitamente un valore di un tipo in un altro tipo, ricorrendo ad alcune funzioni predefinite.
Ad esempio, la funzione parseInt() converte una stringa in un valore intero. La funzione prevede due parametri: il primo è la stringa da convertire, mentre il secondo è opzionale e indica la base del sistema di rappresentazione numerica utilizzato. I seguenti sono esempi di utilizzo della funzione:
parseInt("12") // 12
parseInt("12abc") // 12
parseInt("a12bc") // NaN
parseInt("12.5") // 12
parseInt("12", 8) // il valore di 12 nel sistema di numerazione ottale (base 8), cioè 10
Da notare come la presenza di caratteri non numerici in coda alla stringa venga ignorata, mentre impedisce di fatto la conversione se questi si trovano all'inizio.
La funzione parseFloat() restituisce un valore numerico considerando l'eventuale virgola:
parseFloat("12") //12
parseFloat("12.5") //12.5
È possibile effettuare conversioni esplicite tra gli altri tipi di dato ricorrendo agli oggetti, come vedremo più avanti.
typeof, verificare il tipo delle variabili
In qualsiasi momento, comunque, si può verificare il tipo della variabile (o di un valore oppure di un letterale) tramite l'operatore typeof. Ad esempio:
typeof 69
Restituisce la stringa "number"
. I valori restituiti possono essere: "string"
, "boolean"
, "number"
, "function"
, "object"
, "undefined"
, "xml"
.
Ad esempio immaginiamo di avere le seguenti variabili:
var prova = new Function();
var numero = 1;
var carattere = "Salve";
console.log(typeof prova); // ritorna "function"
console.log(typeof numero); // ritorna "number"
console.log(typeof carattere); // ritorna "string"