¿Por qué 2 == [2] en JavaScript?

164

Recientemente descubrí eso 2 == [2]en JavaScript. Resulta que esta peculiaridad tiene un par de consecuencias interesantes:

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

Del mismo modo, los siguientes trabajos:

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

Aún más extraño, esto también funciona:

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

Estos comportamientos parecen consistentes en todos los navegadores.

¿Alguna idea de por qué esta es una característica del lenguaje?

Aquí hay consecuencias más locas de esta "característica":

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

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

Estos ejemplos fueron encontrados por jimbojw http://jimbojw.com fama, así como walkingeyerobot .

Xavi
fuente

Respuestas:

134

Puede buscar el algoritmo de comparación en la especificación ECMA (secciones relevantes de ECMA-262, 3a edición para su problema: 11.9.3, 9.1, 8.6.2.6).

Si traduce los algoritmos abstractos involucrados nuevamente a JS, lo que sucede al evaluar 2 == [2]es básicamente esto:

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

donde valueOf()for arrays devuelve la matriz en sí y la representación de cadena de una matriz de un elemento es la representación de cadena del elemento individual.

Esto también explica el tercer ejemplo, ya que [[[[[[[2]]]]]]].toString()sigue siendo solo la cadena 2.

Como puede ver, hay mucha magia detrás de escena involucrada, por lo que generalmente solo uso el operador de igualdad estricta ===.

El primer y segundo ejemplo son más fáciles de seguir ya que los nombres de las propiedades siempre son cadenas, por lo que

a[[2]]

es equivalente a

a[[2].toString()]

que es solo

a["2"]

Tenga en cuenta que incluso las teclas numéricas se tratan como nombres de propiedad (es decir, cadenas) antes de que ocurra cualquier magia de matriz.

Christoph
fuente
10

Se debe a la conversión de tipo implícito del ==operador.

[2] se convierte en Número 2 cuando se compara con un Número. Pruebe el +operador unario en [2].

> +[2]
2
Chetan S
fuente
Otros dicen que [2] se convierte en una cadena. +"2"es también el número 2.
dlamblin
1
En realidad, no es tan fácil. [2] se convierte en cadena estaría más cerca, pero eche un vistazo 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

En el lado derecho de la ecuación, tenemos a [2], que devuelve un tipo de número con valor 2. A la izquierda, primero estamos creando una nueva matriz con un solo objeto de 2. Luego estamos llamando a [( matriz está aquí)]. No estoy seguro de si esto se evalúa como una cadena o un número. 2 o "2". Tomemos el caso de la cuerda primero. Creo que un ["2"] crearía una nueva variable y devolvería nulo. null! == 2. Entonces supongamos que se está convirtiendo implícitamente en un número. a [2] devolvería 2. 2 y 2 coinciden en tipo (entonces === funciona) y valor. Creo que está convirtiendo implícitamente la matriz en un número porque un [valor] espera una cadena o número. Parece que el número tiene mayor prioridad.

En una nota al margen, me pregunto quién determina esa precedencia. ¿Es porque [2] tiene un número como primer elemento, por lo que se convierte en un número? ¿O es que al pasar una matriz a una [matriz], primero intenta convertir la matriz en un número y luego en una cadena. ¿Quién sabe?

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

En este ejemplo, está creando un objeto llamado a con un miembro llamado abc. El lado derecho de la ecuación es bastante simple; es equivalente a a.abc. Esto devuelve 1. El lado izquierdo primero crea una matriz literal de ["abc"]. Luego busca una variable en un objeto pasando la matriz recién creada. Como esto espera una cadena, convierte la matriz en una cadena. Esto ahora se evalúa como un ["abc"], que es igual a 1. 1 y 1 son del mismo tipo (razón por la cual === funciona) e igual valor.

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

Esto es solo una conversión implícita. === no funcionaría en esta situación porque hay un desajuste de tipo.

Shawn
fuente
La respuesta a su pregunta sobre la precedencia: se ==aplica ToPrimitive()a la matriz, que a su vez invoca su toString()método, por lo que lo que realmente compara es el número 2con la cadena "2"; La comparación entre una cadena y un número se realiza mediante la conversión de la cadena
Christoph
8

Para el ==caso, esta es la razón por la cual Doug Crockford recomienda usar siempre ===. No realiza ninguna conversión de tipo implícito.

Para los ejemplos con ===, la conversión de tipo implícito se realiza antes de que se llame al operador de igualdad.

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

Eso es interesante, no es que [0] sea verdadero y falso, en realidad

[0] == true // false

Es la forma divertida de javascript de procesar el operador if ().

Alexander Abramov
fuente
44
en realidad, es la forma divertida de ==trabajar; si usa un reparto explícito real (es decir Boolean([0])o!![0] ), usted encontrará que [0]se evaluará como trueen contextos booleanos como debe ser: en JS, se considera cualquier objetotrue
Christoph
6

Una matriz de un elemento puede tratarse como el elemento mismo.

Esto se debe a la escritura del pato. Desde "2" == 2 == [2] y posiblemente más.

Ólafur Waage
fuente
44
porque no coinciden en tipo. en el primer ejemplo, el lado izquierdo se evalúa primero y terminan haciendo coincidir el tipo.
Shawn
8
Además, no creo que escribir patos sea la palabra correcta aquí. Tiene más que ver con la conversión de tipo implícita realizada por el ==operador antes de comparar.
Chetan S
14
esto no tiene nada que ver con la tipificación de pato, sino más bien con la tipificación débil, es decir, la conversión de tipos implícita
Christoph
@Chetan: lo que dijo;)
Christoph
2
Lo que dijeron Chetan y Christoph.
Tim Down
3

Para agregar un pequeño detalle a las otras respuestas ... al comparar una Arraycon una Number, Javascript convertirá la Arraycon parseFloat(array). Puede probarlo usted mismo en la consola (por ejemplo, Firebug o Web Inspector) para ver a qué diferentes Arrayvalores se convierten.

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

Para Arrays, parseFloatrealiza la operación en el Arrayprimer miembro del 's y descarta el resto.

Editar: según los detalles de Christoph, puede ser que esté usando la forma más larga internamente, pero los resultados son consistentemente idénticos parseFloat, por lo que siempre puede usar parseFloat(array)como abreviatura para saber con certeza cómo se convertirá.

sin párpados
fuente
2

Estás comparando 2 objetos en cada caso. No uses ==, si estás pensando en una comparación, tienes === en mente y no ==. == a menudo puede dar efectos locos. Busque las partes buenas en el idioma :)

Jaseem
fuente
0

Explicación de la sección EDITAR de la pregunta:

1er ejemplo

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

Primero encasillado [0] a un valor primitivo según la respuesta de Christoph anterior tenemos "0" ( [0].valueOf().toString())

"0" == false

Ahora, escriba Boolean (falso) a Number y luego String ("0") a Number

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

En cuanto a la ifdeclaración, si no hay una comparación explícita en la condición if en sí, la condición se evalúa como verdadera valores de .

Solo hay 6 valores falsos: falso, nulo, indefinido, 0, NaN y cadena vacía "". Y cualquier cosa que no sea un valor falso es un valor verdadero.

Como [0] no es un valor falso, es un valor verdadero, la ifdeclaración se evalúa como verdadera y ejecuta la declaración.


2do ejemplo

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

De nuevo, escriba fundir los valores a 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
n4m31ess_c0d3r
fuente