Pourquoi 2 == [2] en JavaScript?

164

J'ai récemment découvert cela 2 == [2]en JavaScript. En fait, cette bizarrerie a quelques conséquences intéressantes:

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

De même, les travaux suivants:

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

Encore plus étrange encore, cela fonctionne aussi:

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

Ces comportements semblent cohérents sur tous les navigateurs.

Une idée de la raison pour laquelle il s'agit d'une fonctionnalité linguistique?

Voici les conséquences plus insensées de cette "fonctionnalité":

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

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

Ces exemples ont été trouvés par jimbojw http://jimbojw.com fame ainsi que walkingeyerobot .

Xavi
la source

Réponses:

134

Vous pouvez rechercher l'algorithme de comparaison dans la spécification ECMA (sections pertinentes de l'ECMA-262, 3e édition pour votre problème: 11.9.3, 9.1, 8.6.2.6).

Si vous traduisez les algorithmes abstraits impliqués en JS, ce qui se passe lors de l'évaluation 2 == [2]est essentiellement ceci:

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

valueOf() for arrays renvoie le tableau lui-même et la représentation sous forme de chaîne d'un tableau à un élément est la représentation sous forme de chaîne de l'élément unique.

Cela explique également le troisième exemple car [[[[[[[2]]]]]]].toString()c'est encore juste la chaîne2 .

Comme vous pouvez le voir, il y a beaucoup de magie derrière la scène impliquée, c'est pourquoi je n'utilise généralement que l'opérateur d'égalité stricte === .

Le premier et le deuxième exemple sont plus faciles à suivre car les noms de propriété sont toujours des chaînes, donc

a[[2]]

est équivalent à

a[[2].toString()]

qui est juste

a["2"]

Gardez à l'esprit que même les clés numériques sont traitées comme des noms de propriétés (c'est-à-dire des chaînes) avant que toute magie de tableau ne se produise.

Christoph
la source
10

C'est à cause de la conversion de type implicite de l' ==opérateur.

[2] est converti en nombre est 2 par rapport à un nombre. Essayez l' +opérateur unaire sur [2].

> +[2]
2
Chetan S
la source
D'autres disent que [2] est converti en chaîne. +"2"est aussi le numéro 2.
dlamblin
1
En fait, ce n'est pas si simple. [2] est converti en chaîne serait plus proche, mais jetez un œil à 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

Sur le côté droit de l'équation, nous avons le a [2], qui renvoie un type de nombre avec la valeur 2. Sur la gauche, nous créons d'abord un nouveau tableau avec un seul objet de 2. Ensuite, nous appelons a [( tableau est ici)]. Je ne sais pas si cela correspond à une chaîne ou à un nombre. 2 ou "2". Prenons d'abord la casse de la chaîne. Je crois qu'un ["2"] créerait une nouvelle variable et renverrait null. null! == 2. Supposons donc qu'il se convertit implicitement en un nombre. a [2] renverrait 2. 2 et 2 correspondent au type (donc === fonctionne) et à la valeur. Je pense qu'il convertit implicitement le tableau en un nombre car une [valeur] attend une chaîne ou un nombre. Il semble que le nombre ait une priorité plus élevée.

En passant, je me demande qui détermine cette préséance. Est-ce parce que [2] a un nombre comme premier élément, donc il se convertit en un nombre? Ou est-ce que lors du passage d'un tableau dans un [tableau], il essaie d'abord de transformer le tableau en nombre, puis en chaîne. Qui sait?

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

Dans cet exemple, vous créez un objet appelé a avec un membre appelé abc. Le côté droit de l'équation est assez simple; c'est équivalent à a.abc. Cela renvoie 1. Le côté gauche crée d'abord un tableau littéral de ["abc"]. Vous recherchez ensuite une variable sur un objet en passant le tableau nouvellement créé. Comme cela attend une chaîne, il convertit le tableau en une chaîne. Cela évalue maintenant à un ["abc"], qui est égal à 1. 1 et 1 sont du même type (c'est pourquoi === fonctionne) et de valeur égale.

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

Ceci est juste une conversion implicite. === ne fonctionnerait pas dans cette situation car il y a une incompatibilité de type.

Shawn
la source
La réponse à votre question sur la préséance: ==s'applique ToPrimitive()au tableau, qui à son tour invoque sa toString()méthode, donc ce que vous comparez réellement est le nombre 2à la chaîne "2"; la comparaison entre une chaîne et un nombre se fait en convertissant la chaîne
Christoph
8

Pour le ==cas, c'est pourquoi Doug Crockford recommande de toujours utiliser ===. Il ne fait aucune conversion de type implicite.

Pour les exemples avec ===, la conversion de type implicite est effectuée avant l'appel de l'opérateur d'égalité.

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

C'est intéressant, ce n'est pas que [0] soit à la fois vrai et faux, en fait

[0] == true // false

C'est la manière amusante de javascript de traiter l'opérateur if ().

Alexandre Abramov
la source
4
en fait, c'est la façon amusante de ==fonctionner; si vous utilisez un cast explicite réel (c'est-à Boolean([0])- dire ou !![0]), vous constaterez que cela [0]sera évalué truedans des contextes booléens comme il se doit: dans JS, tout objet est considérétrue
Christoph
6

Un tableau d'un élément peut être traité comme l'élément lui-même.

Cela est dû au typage du canard. Depuis "2" == 2 == [2] et peut-être plus.

Ólafur Waage
la source
4
parce qu'ils ne correspondent pas au type. dans le premier exemple, le côté gauche est évalué en premier et ils finissent par correspondre au type.
Shawn
8
De plus, je ne pense pas que le typage canard soit le mot correct ici. Il s'agit davantage de la conversion de type implicite effectuée par l' ==opérateur avant la comparaison.
Chetan S
14
cela n'a rien à voir avec le typage de canard mais plutôt avec un typage faible, c'est-à-dire une conversion de type implicite
Christoph
@Chetan: ce qu'il a dit;)
Christoph
2
Ce que Chetan et Christoph ont dit.
Tim Down
3

Pour ajouter un peu de détail aux autres réponses ... lorsque vous comparez un Arrayà un Number, Javascript convertira le Arrayavec parseFloat(array). Vous pouvez l'essayer vous-même dans la console (par exemple Firebug ou Web Inspector) pour voir en quoi les différentes Arrayvaleurs sont converties.

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

Pour Arrays, parseFloatexécute l'opération sur le Arraypremier membre de s, et rejette le reste.

Edit: Selon les détails de Christoph, il se peut qu'il utilise le formulaire le plus long en interne, mais les résultats sont toujours identiques à parseFloat, vous pouvez donc toujours utiliser parseFloat(array)comme raccourci pour savoir avec certitude comment il sera converti.

paupière
la source
2

Vous comparez 2 objets dans chaque cas. N'utilisez pas ==, si vous pensez à la comparaison, vous avez === en tête et non ==. == peut souvent donner des effets insensés. Cherchez les bonnes parties dans la langue :)

Jaseem
la source
0

Explication de la section EDIT de la question:

1er exemple

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

Premier typage [0] à une valeur primitive selon la réponse de Christoph ci-dessus, nous avons "0" ( [0].valueOf().toString())

"0" == false

Maintenant, tapez Boolean (false) en Number, puis String ("0") en Number

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

Comme pour l' ifinstruction, s'il n'y a pas de comparaison explicite dans la condition if elle-même, la condition évalue les valeurs de vérité .

Il n'y a que 6 valeurs fausses: false, null, undefined, 0, NaN et une chaîne vide "". Et tout ce qui n'est pas une valeur fausse est une valeur de vérité.

Puisque [0] n'est pas une valeur falsifiée, c'est une valeur de vérité, l' ifinstruction prend la valeur true & exécute l'instruction.


2ème exemple

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

Encore une fois, tapez les valeurs de conversion primitive

    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
la source