L'uso di == in JavaScript ha mai senso?


276

In JavaScript, le parti buone , Douglas Crockford ha scritto:

JavaScript ha due serie di operatori di uguaglianza: ===e !==, e i loro gemelli malvagi ==e !=. I buoni funzionano come ti aspetteresti. Se i due operandi sono dello stesso tipo e hanno lo stesso valore, ===produce truee !==produce false. I gemelli malvagi fanno la cosa giusta quando gli operandi sono dello stesso tipo, ma se sono di tipi diversi, cercano di forzare i valori. Le regole con cui lo fanno sono complicate e non memorabili. Questi sono alcuni dei casi interessanti:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

La mancanza di transitività è allarmante. Il mio consiglio è di non usare mai i gemelli malvagi. Invece, usa sempre ===e !==. Tutti i confronti appena mostrati producono falsecon l' ===operatore.

Data questa inequivocabile osservazione, c'è mai un momento in cui l'uso ==potrebbe effettivamente essere appropriato?


11
Ha senso in molti posti. Ogni volta che è ovvio che stai confrontando due cose dello stesso tipo (questo accade molto nella mia esperienza), == vs === dipende solo dalle preferenze. Altre volte in realtà vuoi un confronto astratto (come il caso che menzioni nella tua risposta). Se è appropriato dipende dalle convenzioni per ogni dato progetto.
Ehi,

4
Per quanto riguarda "un sacco di posti", nella mia esperienza i casi in cui non importa oltre i casi in cui lo fa. La tua esperienza potrebbe essere diversa; forse abbiamo esperienza con diversi tipi di progetti. Quando guardo i progetti che utilizzano ==per impostazione predefinita, ===si distingue e mi fa sapere che sta succedendo qualcosa di importante.
Ehi,

4
Non credo che JavaScript vada abbastanza lontano con la sua coercizione di tipo. Dovrebbe avere ancora più opzioni di coercizione di tipo, proprio come il linguaggio BS .
Mark Booth,

5
Un posto che uso == è quando si confrontano gli ID a discesa (che sono sempre caratteri) con gli ID modello (che sono comunemente int).
Scottie,

12
Lo sviluppo Web di @DevSolar ha senso quando non si vuole avere a che fare con la produzione di un'app nativa per ognuna delle 15 piattaforme e con la certificazione sull'App Store monopolistico di ciascuna piattaforma che ne ha una.
Damian Yerrick,

Risposte:


232

Ho intenzione di fare una discussione per ==

Douglas Crockford, che hai citato, è noto per le sue numerose e spesso utilissime opinioni. Mentre sono con Crockford in questo caso particolare, vale la pena ricordare che non è l' unica opinione. Ci sono altri come il creatore del linguaggio Brendan Eich che non vede il grosso problema ==. L'argomento va un po 'come il seguente:

JavaScript è un linguaggio comportamentale *. Le cose vengono trattate in base a ciò che possono fare e non al loro tipo reale. Questo è il motivo per cui è possibile chiamare il .mapmetodo di un array su un NodeList o su un set di selezione jQuery. È anche il motivo per cui puoi fare 3 - "5"e ottenere qualcosa di significativo, perché "5" può agire come un numero.

Quando esegui ==un'uguaglianza, stai confrontando il contenuto di una variabile anziché il suo tipo . Ecco alcuni casi in cui questo è utile:

  • Lettura di un numero dall'utente : leggi il .valuedi un elemento di input nel DOM? Nessun problema! Non devi iniziare a lanciarlo o preoccuparti del suo tipo: puoi ==farlo subito ai numeri e ottenere qualcosa di significativo.
  • Devi verificare "l'esistenza" di una variabile dichiarata? - puoi == nullfarlo dal momento che nullrappresenta comportamentalmente non c'è nulla e undefined non ha nulla anche lì.
  • Devi verificare se hai ricevuto input significativi da un utente? - controlla se l'input è falso con l' ==argomento, tratterà i casi in cui l'utente non ha inserito nulla o solo uno spazio bianco per te che è probabilmente quello che ti serve.

Diamo un'occhiata agli esempi di Crockford e spiegali in modo comportamentale:

'' == '0'           // got input from user vs. didn't get input - so false
0 == ''             // number representing empty and string representing empty - so true
0 == '0'            // these both behave as the number 0 when added to numbers - so true    
false == 'false'    // false vs got input from user which is truthy - so false
false == '0'        // both can substitute for 0 as numbers - so again true

false == undefined  // having nothing is not the same as having a false value - so false
false == null       // having empty is not the same as having a false value - so false
null == undefined   // both don't represent a value - so true

' \t\r\n ' == 0     // didn't get meaningful input from user vs falsey number - true 

Fondamentalmente, ==è progettato per funzionare in base al comportamento dei primitivi in JavaScript e non a quello che sono . Anche se non sono personalmente d'accordo con questo punto di vista, c'è sicuramente un merito nel farlo, specialmente se si prende questo paradigma di trattamento dei tipi basato sul comportamento in tutto il linguaggio.

* alcuni potrebbero preferire la tipizzazione strutturale del nome che è più comune ma c'è una differenza - non è davvero interessato a discutere la differenza qui.


8
Questa è un'ottima risposta e io uso tutti e tre i casi d'uso "for ==". # 1 e # 3 sono particolarmente utili.
Chris Cirefice,

224
Il problema ==non è che nessuno dei suoi confronti è utile , è che le regole sono impossibili da ricordare, quindi sei quasi sicuro di fare errori. Ad esempio: "Devi verificare se hai ricevuto input significativi da un utente?", Ma "0" è un input significativo ed '0'==falseè vero. Se avessi usato ===avresti dovuto pensarci esplicitamente e non avresti fatto l'errore.
Timmmm,

44
"le regole sono impossibili da ricordare" <== questa è l'unica cosa che mi spaventa di fare qualsiasi cosa "significativa" in Javascript. (e float la matematica che porta a problemi negli esercizi di
calcolo di

9
L' 3 - "5"esempio solleva un buon punto da solo: anche se lo usi esclusivamente ===per il confronto, questo è solo il modo in cui le variabili funzionano in Javascript. Non c'è modo di sfuggire completamente.
Jarett Millard,

23
@Timmmm nota che sto giocando l'avvocato del diavolo qui. Non uso ==nel mio codice, lo trovo un anti-pattern e sono completamente d'accordo che l'algoritmo astratto di uguaglianza è difficile da ricordare. Quello che sto facendo qui è argomentare le persone.
Benjamin Gruenbaum,

95

Si scopre che jQuery usa il costrutto

if (someObj == null) {
  // do something
}

ampiamente, come scorciatoia per il codice equivalente:

if ((someObj === undefined) || (someObj === null))  {
  // do something
}

Questa è una conseguenza della specifica del linguaggio ECMASc § § 11.9.3, The Abstract Equality Comparison Algorithm , che afferma, tra le altre cose, che

1.  If Type(x) is the same as Type(y), then  
    a.  If Type(x) is Undefined, return true.  
    b.  If Type(x) is Null, return true.

e

2.  If x is null and y is undefined, return true.
3.  If x is undefined and y is null, return true.

Questa particolare tecnica è abbastanza comune che JSHint ha una bandiera appositamente progettata per questo.


10
Non è giusto rispondere alla tua domanda a cui volevo rispondere :) == null or undefinedè l'unico posto in cui non uso ===o!==
invii il

26
Ad essere onesti, jQuery non è certo un modello di base di codice. Dopo aver letto diverse volte la fonte jQuery è uno dei miei codebase meno favoriti con molti ternari nidificati, bit poco chiari, annidamenti e cose che altrimenti eviterei nel codice reale. Non credetemi , leggetelo github.com/jquery/jquery/tree/master/src e confrontatelo con Zepto che è un clone di jQuery: github.com/madrobby/zepto/tree/master/src
Benjamin Gruenbaum,

4
Si noti inoltre che Zepto sembra predefinito ==e utilizza solo ===nei casi in cui è necessario: github.com/madrobby/zepto/blob/master/src/event.js
Ehi,

2
@Hey a essere onesti Zepto non è nemmeno un modello di base di codice - è tristemente noto per l'uso __proto__e, a sua volta, lo costringe quasi da solo nelle specifiche della lingua per evitare di rompere i siti Web mobili.
Benjamin Gruenbaum,

2
@BenjaminGruenbaum che non è stato un giudizio richiede la qualità della loro base di codice, sottolineando solo che progetti diversi seguono convenzioni diverse.
Ehi,

15

Controllare i valori per nullo undefinedè una cosa, come è stato ampiamente spiegato.

C'è un'altra cosa, dove ==brilla:

Puoi definire il confronto in questo >=modo (la gente di solito parte da >ma lo trovo più elegante):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < be a <= bvengono lasciati come esercizio al lettore.

Come sappiamo, in JavaScript "3" >= 3e "3" <= 3, da cui si ottiene 3 == "3". Puoi sottolineare che è un'idea orribile consentire l'implementazione del confronto tra stringhe e numeri analizzando la stringa. Ma dato che funziona così, ==è assolutamente il modo corretto di implementare quell'operatore di relazione.

Quindi la cosa davvero positiva ==è che è coerente con tutte le altre relazioni. Per dirla diversamente, se scrivi questo:

function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  return 0;
}

Implicitamente stai ==già usando .

Ora alla domanda abbastanza correlata di: è stata una cattiva scelta implementare il confronto di numeri e stringhe nel modo in cui è implementato? Visto in isolamento, sembra una cosa piuttosto stupida da fare. Ma nel contesto di altre parti di JavaScript e DOM, è relativamente pragmatico, considerando che:

  • gli attributi sono sempre stringhe
  • le chiavi sono sempre stringhe (il caso d'uso è che usi un Objectper avere una mappa sparsa da ints a valori)
  • i valori di input e controllo del modulo dell'utente sono sempre stringhe (anche se l'origine corrisponde input[type=number])

Per un numero intero di ragioni aveva senso far sì che le stringhe si comportassero come numeri quando necessario. E supponendo che il confronto delle stringhe e la concatenazione delle stringhe avessero diversi operatori (ad es. ::Per la concatenazione e un metodo per il confronto (dove è possibile utilizzare tutti i tipi di parametri relativi alla distinzione tra maiuscole e minuscole e cosa no)), in realtà sarebbe meno un casino. Ma questo sovraccarico dell'operatore è probabilmente da dove proviene il "Java" in "JavaScript";)


4
Essere onesti >=non è veramente transitivo. È abbastanza possibile in JS che né a > ba < bb == a(per esempio:) NaN.
Benjamin Gruenbaum,

8
@BenjaminGruenbaum: È come dire che +non è realmente commutativo, perché NaN + 5 == NaN + 5non regge. Il punto è che >=funziona con valori numerici per i quali ==funziona in modo coerente. Non dovrebbe sorprendere il fatto che "non un numero" non sia per sua natura non un numero;)
back2dos

4
Quindi il cattivo comportamento di ==è coerente con il cattivo comportamento di >=? Bene, ora vorrei che ci fosse >==...
Eldritch Conundrum,

2
@EldritchConundrum: Come ho cercato di spiegare, il comportamento di >=è piuttosto coerente con il resto della lingua / API standard. Nella sua totalità, JavaScript riesce ad essere più della somma delle sue parti bizzarre. Se desideri un >==, vorresti anche un severo +? In pratica, molte di queste decisioni rendono molte cose molto più facili. Quindi non mi affretterei a giudicarli poveri.
back2dos

2
@EldritchConundrum: Ancora una volta: gli operatori di relazione hanno lo scopo di confrontare valori numerici, in cui un operando può in effetti essere una stringa. Per i tipi di operandi per i quali >=è significativo, ==è ugualmente significativo - tutto qui. Nessuno dice che dovresti confrontarti [[]]con false. In lingue come C il risultato di questo livello di assurdità è un comportamento indefinito. Trattalo allo stesso modo: non farlo. E starai bene. Inoltre non avrai bisogno di ricordare alcuna regola magica. E poi in realtà è piuttosto semplice.
back2dos

8

Come matematico professionista vedo nell'operatore identico di Javscript == ( chiamato anche "confronto astratto", "uguaglianza libera" ) un tentativo di costruire una relazione di equivalenza tra entità, che include essere riflessivo , simmetrico e transitivo . Sfortunatamente, due di queste tre proprietà fondamentali falliscono:

==non è riflessivo :

A == A può essere falso, ad es

NaN == NaN // false

==non è transitivo :

A == Be B == Cinsieme non implicano A == C, ad es

'1' == 1 // true
1 == '01' // true
'1' == '01' // false

Solo la proprietà simmetrica sopravvive:

A == Bimplica B == Ache la violazione è probabilmente impensabile in ogni caso e porterebbe a una grave ribellione;)

Perché le relazioni di equivalenza contano?

Perché questo è il tipo di relazione più importante e prevalente, supportato da numerosi esempi e applicazioni. L'applicazione più importante è la scomposizione delle entità in classi di equivalenza , che è essa stessa un modo molto conveniente e intuitivo di comprendere le relazioni. E l'incapacità di essere equivalenza porta alla mancanza di classi di equivalenza, che a sua volta porta alla mancanza di intuitività e complessità non necessaria che è ben nota.

Perché è un'idea così terribile scrivere ==per una relazione di non equivalenza?

Perché rompe la nostra familiarità e intuizione, poiché letteralmente qualsiasi relazione interessante di somiglianza, uguaglianza, congruenza, isomorfismo, identità ecc. È un'equivalenza.

Tipo di conversione

Invece di fare affidamento su un'equivalenza intuitiva, JavaScript introduce la conversione del tipo:

L'operatore di uguaglianza converte gli operandi se non sono dello stesso tipo, quindi applica un confronto rigoroso.

Ma come viene definita la conversione del tipo? Attraverso una serie di regole complicate con numerose eccezioni?

Tentativo di costruire una relazione di equivalenza

Booleani. Chiaramente truee falsenon sono gli stessi e dovrebbero essere in classi diverse.

Numeri. Fortunatamente, l'uguaglianza dei numeri è già ben definita, in cui due numeri diversi non fanno mai parte della stessa classe di equivalenza. In matematica, cioè. In JavaScript la nozione di numero è in qualche modo deformata dalla presenza del più esotico -0, Infinitye -Infinity. La nostra intuizione matematica lo impone 0e -0dovrebbe essere nella stessa classe (in realtà lo -0 === 0è true), mentre ciascuna delle infinità è una classe separata.

Numeri e booleani. Date le classi numeriche, dove mettiamo i booleani? falsediventa simile a 0, mentre truediventa simile 1ma nessun altro numero:

true == 1 // true
true == 2 // false

C'è qualche logica qui da mettere trueinsieme 1? Certamente 1si distingue, ma lo è anche -1. Io personalmente non vedo alcuna ragione per la conversione truea 1.

E va ancora peggio:

true + 2 // 3
true - 1 // 0

Quindi trueviene convertito in 1tutti i numeri! È logico? È intuitivo? La risposta è lasciata come esercizio fisico;)

Ma che dire di questo:

1 && true // true
2 && true // true

L'unica booleana xcon x && truel'essere trueè x = true. Il che dimostra che entrambi 1e 2(e qualsiasi altro numero diverso da 0) convertono in true! Ciò che mostra è che la nostra conversione fallisce un'altra proprietà importante - essere la biiezione . Ciò significa che due entità diverse possono convertirsi nella stessa. Il che, di per sé, non deve essere un grosso problema. Il grosso problema sorge quando usiamo questa conversione per descrivere una relazione di "uguaglianza" o "uguaglianza libera" di qualunque cosa vogliamo chiamarla. Ma una cosa è chiara: non sarà una relazione di equivalenza e non sarà descritta in modo intuitivo tramite classi di equivalenza.

Ma possiamo fare di meglio?

Almeno matematicamente - sicuramente sì! Una semplice relazione di equivalenza tra valori booleani e numeri potrebbe essere costruita solo falseed 0essendo nella stessa classe. Quindi false == 0sarebbe l'unica uguaglianza libera non banale.

E le stringhe?

Possiamo tagliare le stringhe dagli spazi bianchi all'inizio e alla fine per convertirli in numeri, inoltre possiamo ignorare gli zeri davanti:

'   000 ' == 0 // true
'   0010 ' == 10 // true

Quindi otteniamo una semplice regola per una stringa: tagliare gli spazi bianchi e gli zeri davanti. O otteniamo un numero o una stringa vuota, nel qual caso convertiamo in quel numero o zero. Oppure non otteniamo un numero, nel qual caso non ci convertiamo e quindi non otteniamo nuove relazioni.

In questo modo potremmo effettivamente ottenere una relazione di equivalenza perfetta sull'insieme totale di booleani, numeri e stringhe! Solo che ... i designer JavaScript ovviamente hanno un'altra opinione:

' ' == '' // false

Quindi le due stringhe in cui entrambe si convertono 0sono improvvisamente non simili! Perché o perché? Secondo la regola, le stringhe sono vagamente uguali proprio quando sono rigorosamente uguali! Non solo questa regola rompe la transitività come vediamo, ma è anche ridondante! Qual è lo scopo di creare un altro operatore ==per renderlo strettamente identico all'altro ===?

Conclusione

L'operatore di uguaglianza allentata ==avrebbe potuto essere molto utile se rispettasse alcune leggi matematiche fondamentali. Ma come purtroppo non fa, la sua utilità ne risente.


Che dire NaN? Inoltre, a meno che non venga applicato un formato numerico specifico per il confronto con le stringhe, deve risultare un confronto non intuitivo delle stringhe o una non transitività.
Solomon Ucko,

@ SolomonUcko si NaNcomporta come un cattivo cittadino :-). La transitività può e deve essere confermata per qualsiasi confronto di equivalenza, intuitivo o meno.
Dmitri Zaitsev,

7

Sì, ho riscontrato un caso d'uso, ad esempio quando si confronta una chiave con un valore numerico:

for (var key in obj) {
    var some_number = foo(key, obj[key]);  // or whatever -- this is just an example
    if (key == some_number) {
        blah();
    }
}

Penso che sia molto più naturale eseguire il confronto key == some_numberpiuttosto che come Number(key) === some_numbero come key === String(some_number).


3

Ho incontrato un'applicazione molto utile oggi. Se vuoi confrontare i numeri imbottiti, come i 01normali numeri interi, ==funziona bene. Per esempio:

'01' == 1 // true
'02' == 1 // false

Ti salva rimuovendo lo 0 e convertendolo in un numero intero.


4
Sono abbastanza sicuro che il modo "giusto" di farlo sia '04'-0 === 4, o forseparseInt('04', 10) === 4
ratbum l'

Non sapevo che potresti farlo.
Jon Snow,

7
Ne ho preso molto.
Jon Snow

1
@ratbum o+'01' === 1
Eric Lagergren,

1
'011' == 011 // falsein modalità non rigorosa e SyntaxError in modalità rigorosa. :)
Brian S,

3

So che questa è una risposta tardiva, ma sembra che ci sia qualche possibile confusione su nulle undefined, quale IMHO è ciò che rende il ==male, tanto più che la mancanza di transitività, che è abbastanza male. Tener conto di:

p1.supervisor = 'Alice';
p2.supervisor = 'None';
p3.supervisor = null;
p4.supervisor = undefined;

Cosa significano questi?

  • p1 ha un supervisore il cui nome è "Alice".
  • p2 ha un supervisore il cui nome è "Nessuno".
  • p3esplicitamente, inequivocabilmente, non ha un supervisore .
  • p4può o può avere un supervisore. Non lo sappiamo, non ci interessa, non dovremmo saperlo (problema di privacy?), In quanto non è affar nostro.

Quando usi ==ti stai confondendo nulle undefinedquesto è del tutto improprio. I due termini significano cose completamente diverse! Dire che non ho un supervisore semplicemente perché mi sono rifiutato di dirti chi è il mio supervisore è sbagliato!

Capisco che ci sono programmatori che non si preoccupano di questa differenza tra nulle undefinedo scelgono di usare questi termini in modo diverso. E se il tuo mondo non usa nulle undefinedcorrettamente, o desideri dare la tua interpretazione a questi termini, così sia. Non penso sia una buona idea.

A proposito, non ho alcun problema con entrambi nulled undefinedessere falso! Va benissimo dire

if (p.supervisor) { ... }

e poi nulle undefinedcauserebbe il codice che elabora il supervisore da saltare. È corretto, perché non conosciamo o non abbiamo un supervisore. Tutto bene. Ma le due situazioni non sono uguali . Questo è il motivo per cui ==è sbagliato. Ancora una volta, le cose possono essere false e usate in un modo di scrivere l'anatra, il che è ottimo per i linguaggi dinamici. È proprio JavaScript, Pythonic, Rubyish, ecc. Ma ancora una volta, queste cose NON sono uguali.

E non fatemi parlare su non-transitività: "0x16" == 10, 10 == "10"ma non "10" == "0x16". Sì, JavaScript è di tipo debole. Sì, è coercitivo. Ma la coercizione non dovrebbe mai, mai, applicarsi all'uguaglianza.

A proposito, Crockford ha opinioni forti. Ma sai una cosa? Ha ragione qui!

FWIW Capisco che ci sono, e ho incontrato personalmente, situazioni in cui ==è conveniente! Come prendere l'input di stringa per i numeri e, diciamo, confrontandolo con 0. Tuttavia, questo è hack. Hai convenienza come compromesso per un modello impreciso del mondo.

TL; DR: la falsità è un grande concetto. Non dovrebbe estendersi all'uguaglianza.


Grazie per aver mostrato le diverse situazioni :) Tuttavia, ti stai perdendo p5... l'unica situazione in typeof(p5.supervisor) === typeof(undefined)cui il supervisore non esiste nemmeno come concetto: D
TheCatWhisperer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.