0

I'm trying to figure out why JavaScript has this strange behaviour in comparing the same array:

var array = [0];
console.log(array == array); //true
console.log(array == !array); //true?

The first one is easily done, they are referencing the same object, but the second is a really tricky one, and I'm working on understanding the process.

Please note that I'm aware that this is abstract equality comparison and not strict equality comparison, and I know their differences (I know that using === would lead to false result, but I'm trying to figure out the behaviour with ==).

P.s.: this one was taken from wtfjs.com, and I didn't find out the explanation, so I tried to give it myself and thought it could be "useful".

Niccolò Campolungo
  • 11,824
  • 4
  • 32
  • 39
  • it's because you're using == and not === – vsync Mar 25 '14 at 21:48
  • @vsync I know, but that was not the point, edited. I wanted to know the process that the algorithm followed to come to evaluating `true` :) – Niccolò Campolungo Mar 25 '14 at 21:50
  • But it's really just like every other question that asks about `==`. We could have an endless number of combinations of operators and operands used around `==`. It all just boils down to the same algorithm. – cookie monster Mar 25 '14 at 21:52
  • @cookiemonster you're right, but this specific case works only with `[0] == ![anyarrayilike]`, and for the most cases I found out an explanation, not for this one – Niccolò Campolungo Mar 25 '14 at 21:54
  • 1
    A `==` comparison uses the [*Abstract Equality Comparison Algorithm*](http://ecma-international.org/ecma-262/5.1/#sec-11.9.3), which is fully explained in [ECMA-262 § 11.9.3](http://ecma-international.org/ecma-262/5.1/#sec-11.9.3). – RobG Mar 25 '14 at 22:31

2 Answers2

2

The first equality is simple, it's a comparison between the same object (same reference), so it returns true.

The second one is a bit tricky, so I'll try to explain better below.

TL;DR

For those who are a bit lazy, here is a simple explanation without quoting the spec every step:

[0] == ![0] => we evaluate ![0] first, which yields false(because [0] is a truthy value).

[0] == false => [0] is evaluated to [0].toString() which is "0".

"0" == false => "0" is converted to the number 0; the same is for false, so we obtain:

0 == 0 which is finally true.

Complete explanation

As for the first equality, for the sake of completeness, I quote here the interested part of the spec.

1.f Return true if x and y refer to the same object. Otherwise, return false.

So this returns true, as expected. Now the tricky part:

First of all, we have to evaluate the UnaryExpression on the right:

  1. Let expr be the result of evaluating UnaryExpression.
  2. Let oldValue be ToBoolean (GetValue(expr) ).
  3. If oldValue is true, return false.
  4. Return true.

But ToBoolean uses this algorithm, and GetValue should return either an Object or a non-empty String, so the result of the evaluation is true. Returning to our UnaryExpression, we have !true, so the result of the final evaluation is false.

So we're back at our original comparison, now we are comparing an Object against a Boolean.

7.If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

ToNumber(false) is 0, so now we are comparing Object and Number.

Back to the specs:

9.If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.

Calling ToPrimitive on our array should return its [[DefaultValue]], which should be, according to this kangax's answer, the result of calling toString on the array itself, so we obtain "0".

So, back to our comparison, it has became an equality between a String and a Number.

5.If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

Calling ToNumber on our string "0" yields 0, again we are finally at a simple comparison: 0 == 0.

Final spec step:

1.c.iii If x is the same Number value as y, return true.

And here the result explained.

Community
  • 1
  • 1
Niccolò Campolungo
  • 11,824
  • 4
  • 32
  • 39
  • The interesting part is the `[0].toString()` and why does that conversion happens. – vsync Mar 25 '14 at 21:50
  • Just figured it out, writing a long answer on it! Give me just the time to type, I'm referencing the spec a lot... – Niccolò Campolungo Mar 25 '14 at 21:52
  • 1
    Details here: http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3 – dfsq Mar 25 '14 at 21:54
  • 2
    Actually it's the boolean that is converted first. See [my answer here](http://stackoverflow.com/a/22231226/1048572) for details – Bergi Mar 25 '14 at 21:57
  • Complete answer out, hope that explains more! – Niccolò Campolungo Mar 25 '14 at 22:02
  • @LightStyle—`we evaluate ![0] first` no, "we" don't. Please read ECMA-262 regarding the `==` operator ([§ 11.9.1](http://ecma-international.org/ecma-262/5.1/#sec-11.9.1)). The left hand side is **always** evaluated first. – RobG Mar 25 '14 at 22:35
  • @RobG: Take a big step back and look at the big picture. I'm sure he means that the operand is converted *ToBoolean* before entering the *Abstract Equality Comparison Algorithm*, so the effective comparison is `[0] == false`. And Bergi is pointing out that `"0" == false` is incorrect WRT the order in which the algorithm performs conversion. At that point it should be `[0] == 0` since the boolean will be converted before the object. So the order described in the answer is incorrect. – cookie monster Mar 26 '14 at 03:08
  • 1
    @cookiemonster—then "first" should be "before doing the comparison" since the expression on the left hand side of `==` has already been evaluated. Comment on Bergi's comment deleted, it was ambiguous as there are two boolean conversions (one to, one from) and I assumed he was talking about the first. :-) – RobG Mar 26 '14 at 03:17
0

The algorithm for the == operator evaluates the expressions from left to right, so given:

var array = [0];

and evaluating:

array == !array;

then first the left hand expression is evaluated and an array is returned. Then the right hand expression is evaluated: ToBoolean is applied to array and, since it's an object, it returns true and the ! operator reverses that to false.

Then the abstract equailty comparison algorithm is used. Again, the left hand side is evaluated first. Since array is an Object and not a Boolean, String or Number, step 7 is used and the right hand side is converted to a Number and the comparison becomes:

array == 0;

The algorithm is run again and gets to step 9, where array is converted to a primitive (string in this case) and the comparison becomes:

'0' == 0;

The algorithm is run again and gets to step 5 where the left hand side is converted to a Number and the comparison becomes:

0 == 0;

The algorithm is run again and this time the expressions have the same Type (Number) so step 1.iii.c is used to return true.

Please note that through all of this, the left hand side is always evaluated first, though sometimes that results in the right hand side being modified and not the left (e.g. at step 7 of the algorithm).

RobG
  • 142,382
  • 31
  • 172
  • 209