Por que 2 == [2] em JavaScript?

164

Eu descobri recentemente isso 2 == [2]em JavaScript. Acontece que essa peculiaridade tem algumas consequências interessantes:

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

Da mesma forma, o seguinte funciona:

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

Ainda mais estranho, isso também funciona:

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

Esses comportamentos parecem consistentes em todos os navegadores.

Alguma idéia de por que esse é um recurso de idioma?

Aqui estão as consequências mais insanas desse "recurso":

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

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

Estes exemplos foram encontrados por jimbojw http://jimbojw.com fama bem como walkingeyerobot .

Xavi
fonte

Respostas:

134

Você pode procurar o algoritmo de comparação na especificação do ECMA (seções relevantes do ECMA-262, 3ª edição para o seu problema: 11.9.3, 9.1, 8.6.2.6).

Se você converter os algoritmos abstratos envolvidos de volta para JS, o que acontece ao avaliar 2 == [2]é basicamente o seguinte:

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

onde valueOf()para matrizes retorna a própria matriz e a representação em cadeia de uma matriz de um elemento é a representação em cadeia do elemento único.

Isso também explica o terceiro exemplo, pois [[[[[[[2]]]]]]].toString()ainda é apenas a string 2.

Como você pode ver, há muita mágica nos bastidores envolvida, e é por isso que geralmente só uso o operador de igualdade estrita ===.

O primeiro e o segundo exemplo são mais fáceis de seguir, pois os nomes das propriedades sempre são strings, portanto

a[[2]]

é equivalente a

a[[2].toString()]

que é apenas

a["2"]

Lembre-se de que mesmo as teclas numéricas são tratadas como nomes de propriedades (ou seja, cadeias) antes que ocorra qualquer mágica de matriz.

Christoph
fonte
10

É por causa da conversão implícita de tipo de ==operador.

[2] é convertido em Número é 2 quando comparado com um Número. Experimente o +operador unário em [2].

> +[2]
2
Chetan S
fonte
Outros estão dizendo que [2] é convertido em uma string. +"2"é também o número 2.
dlamblin
1
Na verdade, não é assim tão fácil. [2] é convertido em string, estaria mais próximo, mas veja 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

No lado direito da equação, temos a [2], que retorna um tipo de número com o valor 2. À esquerda, primeiro criamos uma nova matriz com um único objeto de 2. Em seguida, chamamos um [( matriz está aqui)]. Não tenho certeza se isso é avaliado como uma sequência ou um número. 2 ou "2". Vamos pegar o estojo de cordas primeiro. Eu acredito que um ["2"] criaria uma nova variável e retornaria nulo. null! == 2. Então, vamos supor que ele esteja convertendo implicitamente em um número. a [2] retornaria 2. 2 e 2 correspondem ao tipo (então === funciona) e valor. Eu acho que está implicitamente convertendo a matriz em um número porque um [valor] espera uma string ou número. Parece que o número tem maior precedência.

Em uma nota lateral, pergunto-me quem determina essa precedência. É porque [2] tem um número como seu primeiro item, então ele converte em um número? Ou é que, ao passar um array para um [array], ele tenta transformar o array em um número primeiro e depois em uma string. Quem sabe?

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

Neste exemplo, você está criando um objeto chamado a com um membro chamado abc. O lado direito da equação é bem simples; é equivalente a a.abc. Isso retorna 1. O lado esquerdo primeiro cria uma matriz literal de ["abc"]. Em seguida, você procura uma variável no objeto passando na matriz recém-criada. Como isso espera uma sequência, ele converte a matriz em uma sequência. Isso agora é avaliado como um ["abc"], que é igual a 1. 1 e 1 são do mesmo tipo (e é por isso que === funciona) e igual valor.

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

Esta é apenas uma conversão implícita. === não funcionaria nessa situação porque há uma incompatibilidade de tipo.

Shawn
fonte
A resposta para sua pergunta sobre precedência: ==aplica ToPrimitive()- se à matriz, que por sua vez chama seu toString()método, então o que você realmente compara é o número 2da string "2"; comparação entre uma corda e um número é feito convertendo a string
Christoph
8

Para o ==caso, é por isso que Doug Crockford recomenda sempre o uso ===. Não faz nenhuma conversão implícita de tipo.

Para os exemplos com ===, a conversão implícita de tipo é feita antes que o operador de igualdade seja chamado.

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

Isso é interessante, não é que [0] seja verdadeiro e falso, na verdade

[0] == true // false

É a maneira engraçada de javascript de processar o operador if ().

Alexander Abramov
fonte
4
na verdade, é a maneira engraçada que ==funciona; se você usar uma conversão explícita real (ou seja, Boolean([0])ou !![0]), você verá que [0]irá avaliar a trueem contextos boolean como deveria: em JS, qualquer objeto é consideradotrue
Christoph
6

Uma matriz de um item pode ser tratada como o próprio item.

Isto é devido à digitação de pato. Desde "2" == 2 == [2] e possivelmente mais.

Ólafur Waage
fonte
4
porque eles não correspondem no tipo. no primeiro exemplo, o lado esquerdo é avaliado primeiro e eles acabam correspondendo ao tipo.
Shawn
8
Além disso, não acho que digitar patos seja a palavra correta aqui. Tem mais a ver com a conversão implícita de tipo realizada pelo ==operador antes da comparação.
Chetan S
14
isso não tem nada a ver com tipagem pato, mas sim com tipagem fraca, ou seja, a conversão de tipo implícito
Christoph
@Chetan: o que ele disse;)
Christoph
2
O que Chetan e Christoph disseram.
Tim Tim
3

Para adicionar um pequeno detalhe às outras respostas ... ao comparar um Arraypara um Number, o Javascript converterá o Arraycom parseFloat(array). Você pode tentar você mesmo no console (por exemplo, Firebug ou Web Inspector) para ver para quais Arrayvalores diferentes são convertidos.

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

Para Arrays, parseFloatexecuta a operação no Arrayprimeiro membro do e descarta o restante.

Edit: Pelos detalhes de Christoph, pode ser que ele esteja usando a forma mais longa internamente, mas os resultados são sempre idênticos parseFloat, portanto você sempre pode usar parseFloat(array)como atalho para saber com certeza como será convertido.

ausência de pálpebra
fonte
2

Você está comparando 2 objetos em todos os casos. Não use ==, se você estiver pensando em comparação, estará pensando em === e não ==. == muitas vezes pode dar efeitos insanos. Procure as partes boas da linguagem :)

Jaseem
fonte
0

Explicação para a seção EDIT da pergunta:

1º exemplo

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

Primeiro, digite [0] para um valor primitivo conforme a resposta de Christoph acima, temos "0" ( [0].valueOf().toString())

"0" == false

Agora, digite Boolean (false) para Number e, em seguida, String ("0") para Number

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

Quanto à ifdeclaração, se não houver uma comparação explícita na própria condição if, a condição será avaliada quanto à verdade valores de .

Existem apenas 6 valores falsos: falso, nulo, indefinido, 0, NaN e sequência vazia "". E qualquer coisa que não seja um valor falso é um valor verdadeiro.

Como [0] não é um valor falso, é um valor ifverdadeiro , a instrução é avaliada como verdadeira e executa a instrução.


2º Exemplo

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

Novamente, digite a conversão dos valores para 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
fonte