171

I recently discovered that 2 == [2] in JavaScript. As it turns out, this quirk has a couple of interesting consequences:

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

Similarly, the following works:

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

Even stranger still, this works as well:

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

These behaviors seem consistent across all browsers.

Any idea why this is a language feature?

Here are more insane consequences of this "feature":

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

var a = [0];
a == a // true
a == !a // also true, WTF?
Xavi
  • 20,111
  • 14
  • 72
  • 63
  • 1
    So you discovered that idempotent operations turn out the same output not only when applied just once, but even more. – logi-kal Jan 17 '21 at 10:02

9 Answers9

138

You can look up the comparison algorithm in the ECMA-spec (relevant sections of ECMA-262, 3rd edition for your problem: 11.9.3, 9.1, 8.6.2.6).

If you translate the involved abstract algorithms back to JS, what happens when evaluating 2 == [2] is basically this:

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

where valueOf() for arrays returns the array itself and the string-representation of a one-element array is the string representation of the single element.

This also explains the third example as [[[[[[[2]]]]]]].toString() is still just the string 2.

As you can see, there's quite a lot of behind-the-scene magic involved, which is why I generally only use the strict equality operator ===.

The first and second example are easier to follow as property names are always strings, so

a[[2]]

is equivalent to

a[[2].toString()]

which is just

a["2"]

Keep in mind that even numeric keys are treated as property names (ie strings) before any array-magic happens.

Christoph
  • 164,997
  • 36
  • 182
  • 240
10

It is because of the implicit type conversion of == operator.

[2] is converted to Number is 2 when compared with a Number. Try the unary + operator on [2].

> +[2]
2
Chetan S
  • 23,637
  • 2
  • 63
  • 78
  • Others are saying [2] is converted to a string. `+"2"` is also the number 2. – dlamblin Nov 12 '09 at 19:46
  • 1
    Actually, it's not that easy. [2] is converted to string would be closer but have a look at http://ecma-international.org/ecma-262/5.1/#sec-11.9.3 – neo Aug 01 '13 at 11:47
10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

On the right side of the equation, we have the a[2], which returns a number type with value 2. On the left, we are first creating a new array with a single object of 2. Then we are calling a[(array is in here)]. I am not sure if this evaluates to a string or a number. 2, or "2". Lets take the string case first. I believe a["2"] would create a new variable and return null. null !== 2. So lets assume it is actually implicitly converting to a number. a[2] would return 2. 2 and 2 match in type (so === works) and value. I think it is implicitly converting the array to a number because a[value] expects a string or number. It looks like number takes higher precedence.

On a side note, I wonder who determines that precedence. Is because [2] has a number as it's first item, so it converts to a number? Or is it that when passing an array into a[array] it tries to turn the array into a number first, then string. Who knows?

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

In this example, you are creating an object called a with a member called abc. The right side of the equation is pretty simple; it is equivalent to a.abc. This returns 1. The left side first creates a literal array of ["abc"]. You then search for a variable on the a object by passing in the newly created array. Since this expects a string, it converts the array into a string. This now evaluates to a["abc"], which equals 1. 1 and 1 are the same type (which is why === works) and equal value.

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

This is just an implicit conversion. === wouldn't work in this situation because there is a type mismatch.

Shawn
  • 19,465
  • 20
  • 98
  • 152
  • The answer to your question about precedence: `==` applies `ToPrimitive()` to the array, which in turn invokes its `toString()` method, so what you actually compare is the number `2` to the string `"2"`; comparison between a string and a number is done by converting the string – Christoph Nov 12 '09 at 19:18
8

For the == case, this is why Doug Crockford recommends always using ===. It doesn't do any implicit type conversion.

For the examples with ===, the implicit type conversion is done before the equality operator is called.

Dan Hook
  • 6,769
  • 7
  • 35
  • 52
8
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

That's interesting, it's not that [0] is both true and false, actually

[0] == true // false

It is javascript's funny way of processing if() operator.

Alexander Abramov
  • 1,470
  • 14
  • 20
  • 4
    actually, it's the funny way `==` works; if you use an actual explicit cast (ie `Boolean([0])` or `!![0]`), you'll find that `[0]` will evaluate to `true` in boolean contexts as it should: in JS, any object is considered `true` – Christoph Nov 24 '09 at 23:09
7

A array of one item can be treated as the item itself.

This is due to duck typing. Since "2" == 2 == [2] and possibly more.

Ólafur Waage
  • 68,817
  • 22
  • 142
  • 198
  • 4
    because they dont match in type. in the first example, the left side is evaluated first, and they wind up matching in type. – Shawn Nov 12 '09 at 18:38
  • 8
    Also, I don't think duck-typing is the correct word here. It is more to do with the implicit type conversion performed by `==` operator before comparing. – Chetan S Nov 12 '09 at 19:23
  • 14
    this has nothing to do with duck typing but rather with weak typing, ie implicit type conversion – Christoph Nov 12 '09 at 19:23
  • 2
    What Chetan and Christoph said. – Tim Down Nov 15 '09 at 01:08
3

To add a little detail to the other answers... when comparing an Array to a Number, Javascript will convert the Array with parseFloat(array). You can try it yourself in the console (eg Firebug or Web Inspector) to see what different Array values get converted to.

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

For Arrays, parseFloat performs the operation on the Array's first member, and discards the rest.

Edit: Per Christoph's details, it may be that it is using the longer form internally, but the results are consistently identical to parseFloat, so you can always use parseFloat(array) as shorthand to know for sure how it will be converted.

eyelidlessness
  • 62,413
  • 11
  • 90
  • 94
2

You are comparing 2 objects in every case.. Dont use ==, if you are thinking of comparison, you are having === in mind and not ==. == can often give insane effects. Look for the good parts in the language :)

Jaseem
  • 2,236
  • 6
  • 28
  • 35
1

Explanation for the EDIT section of the question:

1st Example

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

First typecast [0] to a primitive value as per Christoph's answer above we have "0" ([0].valueOf().toString())

"0" == false

Now, typecast Boolean(false) to Number and then String("0") to Number

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

As for if statement, if there is not an explicit comparison in the if condition itself, the condition evaluates for truthy values.

There are only 6 falsy values: false, null, undefined, 0, NaN and empty string "". And anything that is not a falsy value is a truthy value.

Since [0] is not a falsy value, it is a truthy value, the if statement evaluates to true & executes the statement.


2nd Example

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

Again type casting the values to 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
  • 3,028
  • 5
  • 26
  • 35