Conversioni tra tipi di dato in Javascript
- JavaScript è flessibile con i tipi di valori e può convertire automaticamente tra tipi quando necessario.
- Le conversioni tra tipi di dato possono essere implicite o esplicite, a seconda del contesto.
- I valori primitivi possono essere convertiti in stringhe, numeri o booleani, e viceversa.
- Gli oggetti possono essere convertiti in stringhe o numeri usando metodi come
toString()
evalueOf()
. - La conversione da oggetto a primitivo segue un algoritmo che prova
valueOf()
e poitoString()
. - Le conversioni tra tipi di dato possono influenzare il comportamento del codice, specialmente quando si usano operatori di uguaglianza.
- È importante comprendere le regole di conversione per evitare comportamenti inaspettati nel codice.
Conversioni di Tipo
JavaScript è molto flessibile riguardo ai tipi di valori che richiede. Si è visto questo per i booleani: quando JavaScript si aspetta un valore booleano, si può fornire un valore di qualsiasi tipo, e JavaScript lo convertirà secondo necessità. Alcuni valori (valori "truthy") si convertono in true
e altri (valori "falsy") si convertono in false
. Lo stesso vale per altri tipi: se JavaScript vuole una stringa, convertirà qualsiasi valore gli si fornisca in una stringa. Se JavaScript vuole un numero, proverà a convertire il valore che gli si fornisce in un numero (o in NaN
se non può eseguire una conversione significativa).
Alcuni esempi:
10 + " oggetti" // => "10 oggetti": Il numero 10 si converte in una stringa
"7" * "4" // => 28: entrambe le stringhe si convertono in numeri
let n = 1 - "x"; // n == NaN; la stringa "x" non può convertirsi in un numero
n + " oggetti" // => "NaN oggetti": NaN si converte nella stringa "NaN"
La tabella che segue riassume come i valori si convertono da un tipo all'altro in JavaScript. Le voci in grassetto nella tabella evidenziano conversioni che potrebbero risultare sorprendenti. Le celle vuote indicano che non è necessaria alcuna conversione e nessuna viene eseguita:
Valore | in Stringa | in Numero | in Booleano |
---|---|---|---|
undefined |
"undefined" |
NaN |
false |
null |
"null" |
0 |
false |
true |
"true" |
1 |
|
false |
"false" |
0 |
|
"" (stringa vuota) |
0 |
false |
|
"1.2" (non vuota, numerica) |
1.2 |
true |
|
"uno" (non vuota, non numerica) |
NaN |
true |
|
0 |
"0" |
false |
|
-0 |
"0" |
false |
|
1 (finito, non zero) |
"1" |
true |
|
Infinity |
"Infinity" |
true |
|
-Infinity |
"-Infinity" |
true |
|
NaN |
"NaN" |
false |
|
{} (qualsiasi oggetto) |
vedere sotto | vedere sotto | true |
[] (array vuoto) |
"" |
0 |
true |
[9] (un elemento numerico) |
"9" |
9 |
true |
['a'] (qualsiasi altro array) |
usa il metodo join() | NaN |
true |
function(){} (qualsiasi funzione) |
vedere sotto | NaN |
true |
Le conversioni da primitivo a primitivo mostrate nella tabella sono relativamente semplici. La conversione in booleano è già stata discussa nella lezione apposita sui valori booleani. La conversione in stringhe è ben definita per tutti i valori primitivi. La conversione in numeri è solo un po' più complicata. Le stringhe che possono essere analizzate come numeri si convertono in quei numeri. Sono ammessi spazi iniziali e finali, ma qualsiasi carattere iniziale o finale non spazio che non fa parte di un letterale numerico causa la conversione stringa-in-numero per produrre NaN
. Alcune conversioni numeriche possono sembrare sorprendenti: true
si converte in 1, e false
e la stringa vuota si convertono in 0.
La conversione da oggetto a primitivo è un po' più complicata: la analizziamo più avanti in questa lezione.
Conversioni e Uguaglianza
JavaScript ha due operatori che testano se due valori sono uguali. L'"operatore di uguaglianza stretta," ===
, non considera i suoi operandi uguali se non sono dello stesso tipo, e questo è quasi sempre l'operatore giusto da utilizzare quando si programma. Ma poiché JavaScript è così flessibile con le conversioni di tipo, definisce anche l'operatore ==
con una definizione flessibile di uguaglianza. Tutti i seguenti confronti sono veri, ad esempio:
null == undefined // => true: Questi due valori sono trattati come uguali.
"0" == 0 // => true: La stringa si converte in un numero prima del confronto.
0 == false // => true: Il booleano si converte in numero prima del confronto.
"0" == false // => true: Entrambi gli operandi si convertono a 0 prima del confronto!
Si studierà nelle lezioni future esattamente quali conversioni vengono eseguite dall'operatore ==
per determinare se due valori dovrebbero essere considerati uguali.
Si tenga presente che la convertibilità di un valore in un altro non implica l'uguaglianza di quei due valori. Se undefined
viene utilizzato dove è atteso un valore booleano, ad esempio, si convertirà in false
. Ma questo non significa che undefined == false
. Gli operatori e le istruzioni JavaScript si aspettano valori di vari tipi ed eseguono conversioni a quei tipi. L'istruzione if
converte undefined
in false
, ma l'operatore ==
non tenta mai di convertire i suoi operandi in booleani.
Conversioni Esplicite
Sebbene JavaScript esegua molte conversioni di tipo automaticamente, a volte si potrebbe aver bisogno di eseguire una conversione esplicita, o si potrebbe preferire rendere le conversioni esplicite per mantenere il codice più chiaro.
Il modo più semplice per eseguire una conversione di tipo esplicita è utilizzare le funzioni Boolean()
, Number()
e String()
:
Number("3") // => 3
String(false) // => "false": Oppure usa false.toString()
Boolean([]) // => true
Qualsiasi valore diverso da null
o undefined
ha un metodo toString()
, e il risultato di questo metodo è solitamente lo stesso di quello restituito dalla funzione String()
.
Come nota a margine, si noti che le funzioni Boolean()
, Number()
e String()
possono anche essere invocate, attraverso new
, come costruttori. Se si usano in questo modo, si ottiene un oggetto "wrapper" che si comporta proprio come un valore primitivo boolean, number o string. Questi oggetti wrapper sono un residuo storico dei primi giorni di JavaScript, e non c'è mai davvero una buona ragione per usarli.
Certi operatori JavaScript eseguono conversioni di tipo implicite e sono a volte usati esplicitamente per lo scopo della conversione di tipo. Se un operando dell'operatore +
è una stringa, converte l'altro in una stringa. L'operatore unario +
converte il suo operando in un numero. E l'operatore unario !
converte il suo operando in un boolean e lo nega. Questi fatti portano ai seguenti idiomi di conversione di tipo che si potrebbero vedere in qualche codice:
x + "" // => String(x)
+x // => Number(x)
x-0 // => Number(x)
!!x // => Boolean(x): Si noti il doppio !
La formattazione e l'analisi dei numeri sono compiti comuni nei programmi informatici, e JavaScript ha funzioni e metodi specializzati che forniscono un controllo più preciso sulle conversioni da numero-a-stringa e da stringa-a-numero.
Il metodo toString()
definito dalla classe Number accetta un argomento opzionale che specifica una radice, o base, per la conversione. Se non si specifica l'argomento, la conversione viene fatta in base 10. Tuttavia, si possono anche convertire numeri in altre basi (tra 2 e 36). Per esempio:
let n = 17;
let binario = "0b" + n.toString(2); // binario == "0b10001"
let ottale = "0o" + n.toString(8); // ottale == "0o21"
let esadecimale = "0x" + n.toString(16); // esadecimale == "0x11"
Quando si lavora con dati finanziari o scientifici, si potrebbe voler convertire numeri in stringhe in modi che danno controllo sul numero di cifre decimali o il numero di cifre significative nell'output, o si potrebbe voler controllare se viene usata la notazione esponenziale. La classe Number definisce tre metodi per questi tipi di conversioni da numero-a-stringa. toFixed()
converte un numero in una stringa con un numero specificato di cifre dopo la virgola decimale. Non usa mai la notazione esponenziale. toExponential()
converte un numero in una stringa usando la notazione esponenziale, con una cifra prima della virgola decimale e un numero specificato di cifre dopo la virgola decimale (il che significa che il numero di cifre significative è uno più grande del valore che si specifica). toPrecision()
converte un numero in una stringa con il numero di cifre significative che si specifica. Usa la notazione esponenziale se il numero di cifre significative non è abbastanza grande per visualizzare l'intera parte intera del numero. Si noti che tutti e tre i metodi arrotondano le cifre finali o riempiono con zeri quando appropriato. Si considerino i seguenti esempi:
let n = 123456.789;
n.toFixed(0) // => "123457"
n.toFixed(2) // => "123456.79"
n.toFixed(5) // => "123456.78900"
n.toExponential(1) // => "1.2e+5"
n.toExponential(3) // => "1.235e+5"
n.toPrecision(4) // => "1.235e+5"
n.toPrecision(7) // => "123456.8"
n.toPrecision(10) // => "123456.7890"
Oltre ai metodi di formattazione dei numeri mostrati qui, la classe Intl.NumberFormat definisce un metodo di formattazione dei numeri più generale e internazionalizzato.
Se si passa una stringa alla funzione di conversione Number()
, tenta di analizzare quella stringa come un letterale intero o a virgola mobile. Quella funzione funziona solo per interi in base-10 e non permette caratteri finali che non fanno parte del letterale. Le funzioni parseInt()
e parseFloat()
(queste sono funzioni globali, non metodi di alcuna classe) sono più flessibili. parseInt()
analizza solo interi, mentre parseFloat()
analizza sia interi che numeri a virgola mobile. Se una stringa inizia con "0x" o "0X", parseInt()
la interpreta come un numero esadecimale. Sia parseInt()
che parseFloat()
saltano gli spazi iniziali, analizzano quanti più caratteri numerici possono, e ignorano tutto quello che segue. Se il primo carattere non spazio non fa parte di un letterale numerico valido, restituiscono NaN
:
parseInt("3 porcellini") // => 3
parseFloat(" 3.14 metri") // => 3.14
parseInt("-12.34") // => -12
parseInt("0xFF") // => 255
parseInt("0xff") // => 255
parseInt("-0XFF") // => -255
parseFloat(".1") // => 0.1
parseInt("0.1") // => 0
parseInt(".1") // => NaN: gli interi non possono iniziare con "."
parseFloat("$72.47") // => NaN: i numeri non possono iniziare con "$"
parseInt()
accetta un secondo argomento opzionale che specifica la radice (base) del numero da analizzare. I valori legali sono tra 2 e 36. Per esempio:
parseInt("11", 2) // => 3: (1*2 + 1)
parseInt("ff", 16) // => 255: (15*16 + 15)
parseInt("zz", 36) // => 1295: (35*36 + 35)
parseInt("077", 8) // => 63: (7*8 + 7)
parseInt("077", 10) // => 77: (7*10 + 7)
Conversioni da Oggetto a Primitivo
Le sezioni precedenti hanno spiegato come convertire esplicitamente valori da un tipo a un altro tipo e hanno spiegato le conversioni implicite di JavaScript di valori da un tipo primitivo a un altro tipo primitivo. Questa sezione copre le regole complicate che JavaScript usa per convertire oggetti in valori primitivi. È lunga e oscura, e se questa è la prima lettura di questa lezione, ci si dovrebbe sentire liberi di passare alle prossime.
Una ragione per la complessità delle conversioni da oggetto a primitivo di JavaScript è che alcuni tipi di oggetti hanno più di una rappresentazione primitiva. Gli oggetti Date
, per esempio, possono essere rappresentati come stringhe o come timestamp numerici. La specifica JavaScript definisce tre algoritmi fondamentali per convertire oggetti in valori primitivi:
-
prefer-string
Questo algoritmo restituisce un valore primitivo, preferendo un valore stringa, se una conversione a stringa è possibile.
-
prefer-number
Questo algoritmo restituisce un valore primitivo, preferendo un numero, se tale conversione è possibile.
-
no-preference
Questo algoritmo non esprime alcuna preferenza su che tipo di valore primitivo è desiderato, e le classi possono definire le proprie conversioni. Dei tipi JavaScript incorporati, tutti eccetto Date implementano questo algoritmo come prefer-number. La classe Date implementa questo algoritmo come prefer-string.
L'implementazione di questi algoritmi di conversione da oggetto a primitivo è spiegata alla fine di questa sezione. Prima, tuttavia, spieghiamo come gli algoritmi sono usati in JavaScript.
Conversioni da oggetto a booleano
Le conversioni da oggetto a booleano sono banali: tutti gli oggetti si convertono a true
. Si noti che questa conversione non richiede l'uso degli algoritmi da oggetto a primitivo descritti, e che si applica letteralmente a tutti gli oggetti, inclusi gli array vuoti e persino l'oggetto wrapper new Boolean(false)
.
Conversioni da oggetto a stringa
Quando un oggetto deve essere convertito in una stringa, JavaScript prima lo converte in un primitivo utilizzando l'algoritmo prefer-string, poi converte il valore primitivo risultante in una stringa, se necessario, seguendo le regole nella algoritmo prefer-string, poi converte il valore primitivo risultante in una stringa, se necessario, seguendo le regole nella tabella di sopra.
Questo tipo di conversione avviene, ad esempio, se passiamo un oggetto a una funzione built-in che si aspetta un argomento stringa, se chiamiamo String()
come funzione di conversione, e quando interponiamo oggetti nei template literal.
Conversioni da oggetto a numero
Quando un oggetto deve essere convertito in un numero, JavaScript prima lo converte in un valore primitivo utilizzando l'algoritmo prefer-number, poi converte il valore primitivo risultante in un numero, se necessario, seguendo le regole nella tabella di sopra.
Le funzioni e i metodi JavaScript integrati che aspettano argomenti numerici convertono gli argomenti oggetto in numeri in questo modo, e la maggior parte (vedi le eccezioni che seguono) degli operatori JavaScript che aspettano operandi numerici convertono gli oggetti in numeri allo stesso modo.
Conversioni di operatori per casi speciali
Gli operatori sono trattati in dettaglio nelle prossime lezioni. Qui, spieghiamo gli operatori per casi speciali che non utilizzano le conversioni di base da oggetto a stringa e da oggetto a numero descritte in precedenza.
L'operatore +
in JavaScript esegue addizione numerica e concatenazione di stringhe. Se uno dei suoi operandi è un oggetto, JavaScript li converte in valori primitivi utilizzando l'algoritmo no-preference. Una volta che ha due valori primitivi, controlla i loro tipi. Se uno degli argomenti è una stringa, converte l'altro in una stringa e concatena le stringhe. Altrimenti, converte entrambi gli argomenti in numeri e li somma.
Gli operatori ==
e !=
eseguono test di uguaglianza e disuguaglianza in modo flessibile che consente conversioni di tipo. Se un operando è un oggetto e l'altro è un valore primitivo, questi operatori convertono l'oggetto in primitivo utilizzando l'algoritmo no-preference e poi confrontano i due valori primitivi.
Infine, gli operatori relazionali <
, <=
, >
, e >=
confrontano l'ordine dei loro operandi e possono essere utilizzati per confrontare sia numeri che stringhe. Se uno degli operandi è un oggetto, viene convertito in un valore primitivo utilizzando l'algoritmo preferenza-numero. Nota, tuttavia, che a differenza della conversione da oggetto a numero, i valori primitivi restituiti dalla conversione preferenza-numero non vengono poi convertiti in numeri.
Si noti che la rappresentazione numerica degli oggetti Date è significativamente confrontabile con <
e >
, ma la rappresentazione di stringa non lo è. Per gli oggetti Date, l'algoritmo no-preference converte in una stringa, quindi il fatto che JavaScript utilizzi l'algoritmo preferenza-numero per questi operatori significa che si possono utilizzarli per confrontare l'ordine di due oggetti Date.
I metodi toString
e valueOf
Tutti gli oggetti ereditano due metodi di conversione che vengono utilizzati dalle conversioni da oggetto a primitivo, e prima di poter spiegare gli algoritmi di conversione prefer-string, prefer-number e no-preference, si devono spiegare questi due metodi.
Il primo metodo è toString()
, e il suo compito è restituire una rappresentazione stringa dell'oggetto.
({x: 1, y: 2}).toString() // => "[object Object]"
Molte classi definiscono versioni più specifiche del metodo toString()
. Il metodo toString()
della classe Array, per esempio, converte ogni elemento dell'array in una stringa e unisce le stringhe risultanti insieme con virgole in mezzo. Il metodo toString()
della classe Function converte le funzioni definite dall'utente in stringhe di codice sorgente JavaScript. La classe Date definisce un metodo toString()
che restituisce una stringa di data e ora leggibile dall'uomo (e analizzabile da JavaScript). La classe RegExp definisce un metodo toString()
che converte gli oggetti RegExp in una stringa che assomiglia a un letterale RegExp:
[1,2,3].toString() // => "1,2,3"
(function(x) { f(x); }).toString() // => "function(x) { f(x); }"
/\d+/g.toString() // => "/\\d+/g"
let d = new Date(2020,0,1);
d.toString() // => "Wed Jan 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)"
L'altra funzione di conversione degli oggetti è chiamata valueOf()
. Il compito di questo metodo è meno ben definito: dovrebbe convertire un oggetto in un valore primitivo che rappresenta l'oggetto, se esiste un tale valore primitivo. Gli oggetti sono valori composti, e la maggior parte degli oggetti non può realmente essere rappresentata da un singolo valore primitivo, quindi il metodo valueOf()
predefinito restituisce semplicemente l'oggetto stesso piuttosto che restituire un primitivo. Le classi wrapper come String, Number e Boolean definiscono metodi valueOf()
che restituiscono semplicemente il valore primitivo incapsulato. Array, funzioni ed espressioni regolari ereditano semplicemente il metodo predefinito. Chiamare valueOf()
per istanze di questi tipi restituisce semplicemente l'oggetto stesso. La classe Date definisce un metodo valueOf()
che restituisce la data nella sua rappresentazione interna: il numero di millisecondi dal 1° gennaio 1970:
let d = new Date(2010, 0, 1); // 1° gennaio 2010, (ora del Pacifico)
d.valueOf() // => 1262332800000
Algoritmi di conversione da oggetto a primitivo
Con i metodi toString()
e valueOf()
spiegati, possiamo ora spiegare approssimativamente come funzionano i tre algoritmi di conversione da oggetto a primitivo:
-
L'algoritmo prefer-string prova prima il metodo
toString()
.Se il metodo è definito e restituisce un valore primitivo, allora JavaScript usa quel valore primitivo (anche se non è una stringa!). Se
toString()
non esiste o se restituisce un oggetto, allora JavaScript prova il metodovalueOf()
. Se quel metodo esiste e restituisce un valore primitivo, allora JavaScript usa quel valore. Altrimenti, la conversione fallisce con unTypeError
. -
L'algoritmo prefer-number funziona come l'algoritmo prefer-string, eccetto che prova
valueOf()
prima etoString()
secondo. -
L'algoritmo no-preference dipende dalla classe dell'oggetto che viene convertito.
Se l'oggetto è un oggetto Date, allora JavaScript usa l'algoritmo prefer-string. Per qualsiasi altro oggetto, JavaScript usa l'algoritmo prefer-number.
Le regole descritte qui sono vere per tutti i tipi JavaScript integrati e sono le regole predefinite per qualsiasi classe che si definisca. Si vedrà come si possono cambiare queste regole per le proprie classi in una lezione futura.
Prima di lasciare questo argomento, vale la pena notare che i dettagli della conversione prefer-number spiegano perché gli array vuoti si convertono al numero 0 e gli array con un singolo elemento possono anche convertirsi in numeri:
Number([]) // => 0: questo è inaspettato!
Number([99]) // => 99: un numero!?
La conversione da oggetto a numero prima converte l'oggetto in un primitivo usando l'algoritmo prefer-number, poi converte il valore primitivo risultante in un numero. L'algoritmo prefer-number prova valueOf()
prima e poi ricade su toString()
. Ma la classe Array eredita il metodo valueOf()
predefinito, che non restituisce un valore primitivo. Quindi quando si prova a convertire un array in un numero, si finisce per invocare il metodo toString()
dell'array. Gli array vuoti si convertono nella stringa vuota. E la stringa vuota si converte nel numero 0. Un array con un singolo elemento si converte nella stessa stringa in cui si converte quell'unico elemento. Se un array contiene un singolo numero, quel numero viene convertito in una stringa, e poi di nuovo in un numero.