Perché 2 == [2] in JavaScript?


164

Di recente l'ho scoperto 2 == [2]in JavaScript. A quanto pare, questa stranezza ha un paio di conseguenze interessanti:

var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Allo stesso modo, i seguenti lavori:

var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true

Ancora più strano, funziona anche questo:

[[[[[[[2]]]]]]] == 2; // this is true too! WTF?

Questi comportamenti sembrano coerenti in tutti i browser.

Qualche idea sul perché questa sia una funzione linguistica?

Ecco le conseguenze più folli di questa "caratteristica":

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

var a = [0];
a == a // true
a == !a // also true, WTF?

Questi esempi sono stati trovati dalla fama di jimbojw http://jimbojw.com e da walkingeyerobot .

Risposte:


134

Puoi cercare l'algoritmo di confronto nelle specifiche ECMA (sezioni pertinenti di ECMA-262, 3a edizione per il tuo problema: 11.9.3, 9.1, 8.6.2.6).

Se traduci gli algoritmi astratti coinvolti in JS, ciò che accade durante la valutazione 2 == [2]è sostanzialmente questo:

2 === Number([2].valueOf().toString())

dove valueOf()per gli array restituisce l'array stesso e la rappresentazione di stringa di un array ad un elemento è la rappresentazione di stringa del singolo elemento.

Questo spiega anche il terzo esempio poiché [[[[[[[2]]]]]]].toString()è ancora solo la stringa 2.

Come puoi vedere, c'è un sacco di magia dietro le quinte coinvolta, motivo per cui in genere utilizzo solo l'operatore di uguaglianza rigorosa ===.

Il primo e il secondo esempio sono più facili da seguire poiché i nomi delle proprietà sono sempre stringhe, quindi

a[[2]]

è equivalente a

a[[2].toString()]

che è giusto

a["2"]

Tieni presente che anche i tasti numerici vengono trattati come nomi di proprietà (ovvero stringhe) prima che avvenga qualsiasi magia dell'array.


10

È a causa della conversione di tipo implicito ==dell'operatore.

[2] viene convertito in Numero è 2 rispetto a un Numero. Prova l' +operatore unario su [2].

> +[2]
2

Altri dicono che [2] viene convertito in una stringa. +"2"è anche il numero 2.
dlamblin,

1
In realtà, non è così facile. [2] convertito in stringa sarebbe più vicino ma dai un'occhiata a ecma-international.org/ecma-262/5.1/#sec-11.9.3
neo

10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Sul lato destro dell'equazione, abbiamo a [2], che restituisce un tipo di numero con valore 2. A sinistra, stiamo prima creando un nuovo array con un singolo oggetto di 2. Quindi chiamiamo un [( la matrice è qui)]. Non sono sicuro che questo valuti per una stringa o un numero. 2 o "2". Prendiamo prima la custodia delle stringhe. Credo che un ["2"] crei una nuova variabile e restituisca null. null! == 2. Supponiamo quindi che si stia effettivamente convertendo implicitamente in un numero. a [2] restituirebbe 2. 2 e 2 corrispondenze per tipo (quindi === funziona) e valore. Penso che stia implicitamente convertendo l'array in un numero perché un [valore] prevede una stringa o un numero. Sembra che il numero abbia la precedenza più alta.

In una nota a margine, mi chiedo chi determina quella precedenza. Perché [2] ha un numero come primo elemento, quindi viene convertito in un numero? O è che quando si passa un array in un [array] tenta di trasformare prima l'array in un numero, quindi in stringa. Chissà?

var a = { "abc" : 1 };
a[["abc"]] === a["abc"];

In questo esempio, stai creando un oggetto chiamato a con un membro chiamato abc. Il lato destro dell'equazione è piuttosto semplice; è equivalente a a.abc. Ciò restituisce 1. Il lato sinistro crea innanzitutto un array letterale di ["abc"]. Quindi cercare una variabile su un oggetto passando l'array appena creato. Poiché ciò prevede una stringa, converte l'array in una stringa. Questo ora restituisce un ["abc"], che equivale a 1. 1 e 1 sono dello stesso tipo (motivo per cui === funziona) e uguale valore.

[[[[[[[2]]]]]]] == 2; 

Questa è solo una conversione implicita. === non funzionerebbe in questa situazione perché c'è una mancata corrispondenza del tipo.


La risposta alla tua domanda sulla precedenza: si ==applica ToPrimitive()alla matrice, che a sua volta invoca il suo toString()metodo, quindi ciò che si confronta effettivamente è il numero 2con la stringa "2"; il confronto tra una stringa e un numero viene eseguito convertendo la stringa
Christoph,

8

Per il ==caso, questo è il motivo per cui Doug Crockford consiglia di utilizzare sempre ===. Non esegue alcuna conversione di tipo implicita.

Per gli esempi con ===, la conversione del tipo implicito viene eseguita prima che venga chiamato l'operatore di uguaglianza.


7
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

È interessante, non è che [0] sia vero e falso, in realtà

[0] == true // false

È il modo divertente di javascript di elaborare l'operatore if ().


4
in realtà, è il modo divertente ==funziona; se usi un cast esplicito reale (cioè Boolean([0])o !![0]), scoprirai che [0]verrà valutato truein contesti booleani come dovrebbe: in JS, qualsiasi oggetto viene consideratotrue
Christoph,

6

Un array di un oggetto può essere trattato come l'oggetto stesso.

Ciò è dovuto alla digitazione dell'anatra. Da "2" == 2 == [2] e forse di più.


4
perché non corrispondono nel tipo. nel primo esempio, la parte sinistra viene valutata per prima, e finiscono per corrispondere nel tipo.
Shawn,

8
Inoltre, non penso che scrivere la papera sia la parola corretta qui. Ha più a che fare con la conversione di tipo implicita eseguita ==dall'operatore prima del confronto.
Chetan S,

14
questo non ha nulla a che fare con la tipizzazione anatra, ma piuttosto con la digitazione debole, ovvero la conversione implicita del tipo
Christoph,

@Chetan: cosa ha detto;)
Christoph,

2
Cosa hanno detto Chetan e Christoph.
Tim Down,

3

Per aggiungere un piccolo dettaglio alle altre risposte ... quando si confronta an Arraycon a Number, Javascript converte il Arraycon parseFloat(array). Puoi provarlo tu stesso nella console (ad esempio Firebug o Web Inspector) per vedere in quali Arrayvalori diversi vengono convertiti.

parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN

Per Arrays, parseFloatesegue l'operazione sul Arrayprimo membro e scarta il resto.

Modifica: secondo i dettagli di Christoph, è possibile che stia utilizzando internamente la forma più lunga, ma i risultati sono coerentemente identici a parseFloat, quindi puoi sempre usare parseFloat(array)come scorciatoia per sapere con certezza come verrà convertito.


2

Stai confrontando 2 oggetti in ogni caso. Non usare ==, se stai pensando al confronto, stai avendo in mente === e non ==. == può spesso dare effetti folli. Cerca le parti buone nella lingua :)


0

Spiegazione per EDIT sezione della domanda:

1o esempio

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

In primo luogo il typecast [0] su un valore primitivo secondo la risposta di Christoph sopra abbiamo "0" ( [0].valueOf().toString())

"0" == false

Ora, digita booleano (falso) in numero e quindi stringa ("0") in numero

Number("0") == Number(false)
or  0 == 0 
so, [0] == false  // true

Per quanto riguarda l' ifaffermazione, se non esiste un confronto esplicito nella condizione if stessa, la condizione valuta la verità valori di .

Ci sono solo 6 valori di falsy : false, null, undefined, 0, NaN e stringa vuota "". E tutto ciò che non è un valore falso è un valore sincero.

Poiché [0] non è un valore di falsità, è un valore di verità, l' ifistruzione viene valutata vera ed esegue l'istruzione.


2 ° esempio

var a = [0];
a == a // true
a == !a // also true, WTF?

Digita nuovamente il cast dei valori su primitivo,

    a = a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == "0" // true; same type, same value


a == !a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == !"0"
or  "0" == false
or  Number("0") == Number(false)
or  0 = 0   // true
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.