Espressioni Relazionali in JavaScript

Questa lezione descrive gli operatori relazionali di JavaScript. Questi operatori testano una relazione (come "uguale," "minore di," o "proprietà di") tra due valori e restituiscono true o false a seconda che tale relazione esista.

Le espressioni relazionali restituiscono sempre un valore booleano, e tale valore è spesso utilizzato per controllare il flusso di esecuzione del programma nelle istruzioni if, while, e for. Le sottosezioni che seguono documentano gli operatori di uguaglianza e disuguaglianza, gli operatori di confronto, e gli altri due operatori relazionali di JavaScript, in e instanceof.

Concetti Chiave
  • Gli operatori relazionali in JavaScript sono utilizzati per confrontare valori e restituire un risultato booleano.
  • Gli operatori di uguaglianza e disuguaglianza verificano se due valori sono uguali o diversi.
  • Gli operatori di confronto confrontano i valori numerici o stringa.
  • Gli operatori in e instanceof testano la presenza di proprietà e la relazione di prototipo.
  • L'uguaglianza va intesa in due modi: uguaglianza debole (con ==) e uguaglianza forte (con ===).

Operatori di Uguaglianza e Disuguaglianza

Gli operatori == e === verificano se due valori sono uguali, utilizzando due diverse definizioni di uguaglianza. Entrambi gli operatori accettano operandi di qualsiasi tipo, ed entrambi restituiscono true se i loro operandi sono uguali e false se sono diversi. L'operatore === è conosciuto come operatore di uguaglianza rigorosa (o talvolta operatore di identità), e verifica se i suoi due operandi sono "identici" utilizzando una definizione rigorosa di uguaglianza. L'operatore == è conosciuto come operatore di uguaglianza; verifica se i suoi due operandi sono "uguali" utilizzando una definizione più rilassata di uguaglianza che consente conversioni di tipo.

Gli operatori != e !== testano l'esatto opposto degli operatori == e ===. L'operatore di disuguaglianza != restituisce false se due valori sono uguali l'uno all'altro secondo == e restituisce true altrimenti. L'operatore !== restituisce false se due valori sono rigorosamente uguali l'uno all'altro e restituisce true altrimenti. Come vedremo nella prossima lezione, l'operatore ! calcola l'operazione NOT booleana. Questo rende facile ricordare che != e !== stanno per "non uguale a" e "non rigorosamente uguale a."

Gli operatori =, ==, e ===

JavaScript supporta gli operatori =, ==, e ===. Assicuriamoci di comprendere le differenze tra questi operatori di assegnazione, uguaglianza e uguaglianza stretta, e facciamo attenzione a usare quello corretto quando scriviamo codice! Anche se è tentante leggere tutti e tre gli operatori come "uguale", può aiutare a ridurre la confusione se leggiamo "ottiene" o "viene assegnato" per =, "è uguale a" per ==, e "è strettamente uguale a" per ===.

L'operatore == è una caratteristica legacy di JavaScript ed è ampiamente considerato una fonte di bug. Dovremmo quasi sempre usare === invece di ==, e !== invece di !=.

Come menzionato nelle lezioni precedenti, gli oggetti JavaScript sono confrontati per riferimento, non per valore. Un oggetto è uguale a se stesso, ma non a nessun altro oggetto. Se due oggetti distinti hanno lo stesso numero di proprietà, con gli stessi nomi e valori, non sono comunque uguali. Allo stesso modo, due array che hanno gli stessi elementi nello stesso ordine non sono uguali tra loro.

Uguaglianza stretta

L'operatore di uguaglianza stretta === valuta i suoi operandi, poi confronta i due valori come segue, non eseguendo alcuna conversione di tipo:

  • Se i due valori hanno tipi diversi, non sono uguali.
  • Se entrambi i valori sono null o entrambi i valori sono undefined, sono uguali.
  • Se entrambi i valori sono il valore booleano true o entrambi sono il valore booleano false, sono uguali.
  • Se uno o entrambi i valori è NaN, non sono uguali. (Questo è sorprendente, ma il valore NaN non è mai uguale a nessun altro valore, incluso se stesso! Per verificare se un valore x è NaN, usare x !== x, o la funzione globale isNaN()).
  • Se entrambi i valori sono numeri e hanno lo stesso valore, sono uguali. Se un valore è 0 e l'altro è -0, sono anche uguali.
  • Se entrambi i valori sono stringhe e contengono esattamente gli stessi valori a 16-bit nelle stesse posizioni, sono uguali. Se le stringhe differiscono in lunghezza o contenuto, non sono uguali. Due stringhe possono avere lo stesso significato e lo stesso aspetto visivo, ma essere ancora codificate usando sequenze diverse di valori a 16-bit. JavaScript non esegue alcuna normalizzazione Unicode, e una coppia di stringhe come questa non è considerata uguale agli operatori === o ==.
  • Se entrambi i valori si riferiscono allo stesso oggetto, array, o funzione, sono uguali. Se si riferiscono a oggetti diversi, non sono uguali, anche se entrambi gli oggetti hanno proprietà identiche.

Uguaglianza con conversione di tipo

L'operatore di uguaglianza == è simile all'operatore di uguaglianza rigorosa, ma è meno rigoroso. Se i valori dei due operandi non sono dello stesso tipo, tenta alcune conversioni di tipo e prova nuovamente il confronto:

  • Se i due valori hanno lo stesso tipo, li testa per l'uguaglianza rigorosa come descritto in precedenza. Se sono rigorosamente uguali, sono uguali. Se non sono rigorosamente uguali, non sono uguali.
  • Se i due valori non hanno lo stesso tipo, l'operatore == può ancora considerarli uguali. Si usano le seguenti regole e conversioni di tipo per verificare l'uguaglianza:

    • Se un valore è null e l'altro è undefined, sono uguali.
    • Se un valore è un numero e l'altro è una stringa, si converte la stringa in un numero e si prova nuovamente il confronto, usando il valore convertito.
    • Se uno dei valori è true, lo si converte in 1 e si prova nuovamente il confronto. Se uno dei valori è false, lo si converte in 0 e si prova nuovamente il confronto.
    • Se un valore è un oggetto e l'altro è un numero o stringa, si converte l'oggetto in un primitivo usando l'algoritmo descritto nella lezione sulla conversione dei tipi e si prova nuovamente il confronto. Un oggetto è convertito in un valore primitivo tramite il suo metodo toString() o il suo metodo valueOf(). Le classi integrate del JavaScript core tentano la conversione valueOf() prima della conversione toString(), eccetto per la classe Date, che esegue la conversione toString().
    • Qualsiasi altra combinazione di valori non è uguale.

Come esempio di test per l'uguaglianza, consideriamo il confronto:

"1" == true  // => true

Questa espressione restituisce true, indicando che questi valori dall'aspetto molto diverso sono in realtà uguali. Il valore booleano true è prima convertito nel numero 1, e il confronto viene fatto nuovamente. Poi, la stringa "1" è convertita nel numero 1. Poiché entrambi i valori sono ora uguali, il confronto restituisce true.

Operatori di Confronto

Gli operatori di confronto testano l'ordine relativo (numerico o alfabetico) dei loro due operandi:

  • Minore di (<)

    L'operatore < restituisce true se il suo primo operando è minore del secondo operando; altrimenti, restituisce false.

  • Maggiore di (>)

    L'operatore > restituisce true se il suo primo operando è maggiore del secondo operando; altrimenti, restituisce false.

  • Minore o uguale (<=)

    L'operatore <= restituisce true se il suo primo operando è minore o uguale al secondo operando; altrimenti, restituisce false.

  • Maggiore o uguale (>=)

    L'operatore >= restituisce true se il suo primo operando è maggiore o uguale al secondo operando; altrimenti, restituisce false.

Gli operandi di questi operatori di confronto possono essere di qualsiasi tipo. Il confronto può essere eseguito solo su numeri e stringhe, tuttavia, quindi gli operandi che non sono numeri o stringhe vengono convertiti.

Il confronto e la conversione avvengono come segue:

  • Se uno degli operandi si valuta in un oggetto, quell'oggetto viene convertito in un valore primitivo; se il suo metodo valueOf() restituisce un valore primitivo, viene usato quel valore. Altrimenti, viene usato il valore di ritorno del suo metodo toString().
  • Se, dopo qualsiasi conversione richiesta da oggetto a primitivo, entrambi gli operandi sono stringhe, le due stringhe vengono confrontate, usando l'ordine alfabetico, dove "ordine alfabetico" è definito dall'ordine numerico dei valori Unicode a 16 bit che compongono le stringhe.
  • Se, dopo la conversione da oggetto a primitivo, almeno un operando non è una stringa, entrambi gli operandi vengono convertiti in numeri e confrontati numericamente. 0 e -0 sono considerati uguali. Infinity è maggiore di qualsiasi numero diverso da se stesso, e -Infinity è minore di qualsiasi numero diverso da se stesso. Se uno degli operandi è (o si converte in) NaN, allora l'operatore di confronto restituisce sempre false. Anche se gli operatori aritmetici non permettono di mescolare valori BigInt con numeri regolari, gli operatori di confronto permettono confronti tra numeri e BigInt.

Ricordiamo che le stringhe JavaScript sono sequenze di valori interi a 16 bit, e che il confronto di stringhe è solo un confronto numerico dei valori nelle due stringhe. L'ordine di codifica numerico definito da Unicode può non corrispondere all'ordine di collazione tradizionale usato in una particolare lingua o località. Notiamo in particolare che il confronto di stringhe è sensibile al maiuscolo/minuscolo, e tutte le lettere ASCII maiuscole sono "minori di" tutte le lettere ASCII minuscole. Questa regola può causare risultati confusi se non ce la aspettiamo. Per esempio, secondo l'operatore <, la stringa "Zoo" viene prima della stringa "aardvark".

Per un algoritmo di confronto di stringhe più robusto, proviamo il metodo String.localeCompare(), che tiene conto anche delle definizioni specifiche della località per l'ordine alfabetico. Per confronti insensibili al maiuscolo/minuscolo, possiamo convertire le stringhe in tutto minuscolo o tutto maiuscolo usando String.toLowerCase() o String.toUpperCase().

Sia l'operatore + che gli operatori di confronto si comportano diversamente per operandi numerici e di stringa. + favorisce le stringhe: esegue la concatenazione se uno degli operandi è una stringa. Gli operatori di confronto favoriscono i numeri e eseguono il confronto di stringhe solo se entrambi gli operandi sono stringhe:

1 + 2        // => 3: addizione.
"1" + "2"    // => "12": concatenazione.
"1" + 2      // => "12": 2 viene convertito in "2".
11 < 3       // => false: confronto numerico.
"11" < "3"   // => true: confronto di stringhe.
"11" < 3     // => false: confronto numerico, "11" convertito in 11.
"one" < 3    // => false: confronto numerico, "one" convertito in NaN.

Infine, notiamo che gli operatori <= (minore o uguale) e >= (maggiore o uguale) non si basano sugli operatori di uguaglianza o uguaglianza stretta per determinare se due valori sono "uguali". Invece, l'operatore minore-o-uguale è semplicemente definito come "non maggiore di", e l'operatore maggiore-o-uguale è definito come "non minore di". L'unica eccezione si verifica quando uno degli operandi è (o si converte in) NaN, in quel caso, tutti e quattro gli operatori di confronto restituiscono false.

L'operatore in

L'operatore in si aspetta un operando sul lato sinistro che sia una stringa, un simbolo, o un valore che può essere convertito in una stringa. Si aspetta un operando sul lato destro che sia un oggetto. Restituisce true se il valore del lato sinistro è il nome di una proprietà dell'oggetto del lato destro. Per esempio:

let punto = {x: 1, y: 1};  // Definisce un oggetto
"x" in punto               // => true: l'oggetto ha una proprietà chiamata "x"
"z" in punto               // => false: l'oggetto non ha una proprietà "z".
"toString" in punto        // => true: l'oggetto eredita il metodo toString

let dati = [7,8,9];        // Un array con elementi (indici) 0, 1, e 2
"0" in dati                // => true: l'array ha un elemento "0"
1 in dati                  // => true: i numeri vengono convertiti in stringhe
3 in dati                  // => false: nessun elemento 3

L'Operatore instanceof

L'operatore instanceof richiede un operando sinistro che sia un oggetto e un operando destro che identifichi una classe di oggetti. L'operatore restituisce true se l'oggetto sinistro è un'istanza della classe destra e restituisce false altrimenti. Vedremo nelle prossime lezioni che, in JavaScript, le classi di oggetti sono definite dalla funzione costruttore che le inizializza. Pertanto, l'operando destro di instanceof dovrebbe essere una funzione. Ecco alcuni esempi:

let d = new Date();  // Crea un nuovo oggetto con il costruttore Date()
d instanceof Date    // => true: d è stato creato con Date()
d instanceof Object  // => true: tutti gli oggetti sono istanze di Object
d instanceof Number  // => false: d non è un oggetto Number
let a = [1, 2, 3];   // Crea un array con la sintassi letterale di array
a instanceof Array   // => true: a è un array
a instanceof Object  // => true: tutti gli array sono oggetti
a instanceof RegExp  // => false: gli array non sono espressioni regolari

Si noti che tutti gli oggetti sono istanze di Object. instanceof considera le "superclassi" quando decide se un oggetto è un'istanza di una classe. Se l'operando sinistro di instanceof non è un oggetto, instanceof restituisce false. Se il lato destro non è una classe di oggetti, genera un TypeError.

Per comprendere come funziona l'operatore instanceof, è necessario comprendere la "catena dei prototipi". Questo è il meccanismo di ereditarietà di JavaScript, e sarà descritto nelle prossime lezioni. Per valutare l'espressione o instanceof f, JavaScript valuta f.prototype, e poi cerca quel valore nella catena dei prototipi di o. Se lo trova, allora o è un'istanza di f (o di una sottoclasse di f) e l'operatore restituisce true. Se f.prototype non è uno dei valori nella catena dei prototipi di o, allora o non è un'istanza di f e instanceof restituisce false.