Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is happening in this loose equality comparison of 2 empty arrays

I am struggling with understanding how this snippet works on a basic level

if([] == ![]){
console.log("this evaluates to true");
}

Please help me understand where i got it wrong. My thinking:

  1. First there is operator precedence so ! evaluates before ==.
  2. Next ToPrimitive is called and [] converts to empty string.
  3. ! operator notices that it needs to convert "" into boolean so it takes that value and makes it into false then negates into true.
  4. == prefers to compare numbers so in my thinking true makes 1 and [] is converted into "" and then 0

Why does it work then? Where did I get it wrong?

like image 338
Konrad Albrecht Avatar asked Dec 03 '17 19:12

Konrad Albrecht


People also ask

Why are two of the same arrays not equal?

If two distinct objects have the same number of properties, with the same names and values, they are still not equal. Two arrays that have the same elements in the same order are not equal to each other.

Does empty array equal false?

Empty arrays are true but they're also equal to false.

Does an empty array return false JavaScript?

If the length of the object is 0, then the array is considered to be empty and the function will return TRUE. Else the array is not empty and the function will return False.

What is the sum of an empty array?

The sum of values in an array. If the array parameter value is an empty array, returns zero.


2 Answers

Why does it work then?

TLDR:

[] == ![]
        //toBoolean [1]
[] == !true
[] == false
//loose equality round one [2]
//toPrimitive([]); toNumber(false) [3]
"" == 0
//loose equality round two
//toNumber("") [4]
0 === 0
true

Some explanations:

1)

First there is operator precedence so ! evaluates before ==

Negating something calls the internal toBoolean method onto that "something" first. In this case this is an Object (as Arrays are Objects) and for that it always returns true which is then negated.

2)

Now it's up to loose equalities special behaviour ( see Taurus answer for more info) :

If A is an Object ( Arrays are Objects ) and B is a Boolean it will do:

ToPrimitive(A) == ToNumber(B)

3)

  toPrimitive([])

ToPrimitive(A) attempts to convert its Object argument to a primitive value, by attempting to invoke varying sequences of A.toString and A.valueOf methods on A.

Converting an Array to its primitive is done by calling toString ( as they don't have a valueOf method) which is basically join(",").

toNumber(false)

The result is 1 if the argument is true. The result is +0 if the argument is false. Reference

So false is converted to +0

4)

toNumber("")

A StringNumericLiteral that is empty or contains only white space is converted to +0.

So finally "" is converted to +0

Where did I get it wrong?

At step 1. Negating something does not call toPrimitive but toBoolean ...

like image 70
Jonas Wilms Avatar answered Oct 10 '22 18:10

Jonas Wilms


The accepted answer is not correct (it is now, though), see this example:

if([5] == true) {
console.log("hello");	
}

If everything is indeed processed as the accepted answer states, then [5] == true should have evaluated to true as the array [5] will be converted to its string counterpart ("5"), and the string "5" is truthy (Boolean("5") === true is true), so true == true must be true.

But this is clearly not the case because the conditional does not evaluate to true.

So, what's actually happening is:

1. ![] will convert its operand to a boolean and then flip that boolean value, every object is truthy, so ![] is going to evaluate to false.

At this point, the comparison becomes [] == false


2. What gets into play then is these 2 rules, clearly stated in #6 in the specs for the Abstract Equality Comparison algorithm:

  1. If Type(x) is boolean, return the result of the comparison ToNumber(x) == y.
  2. If Type(y) is boolean, return the result of the comparison x == ToNumber(y)

At this point, the comparison becomes [] == 0.


3. Then, it is this rule:

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

As @Jonas_W stated, an array's ToPrimitive will call its toString, which will return a comma-separated list of its contents (I am oversimplifying).

At this point, the comparison becomes "" == 0.


4. And finally (well, almost), this rule:

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

An empty string converted to a number is 0 (Number("") == 0 is true).

At this point, the comparison becomes 0 == 0.


5. At last, this rule will apply:

If Type(x) is the same as Type(y), then
.........
  If Type(x) is Number, then
.........
    If x is the same Number value as y, return true.


And, this is why the comparison evaluates to true. You can also apply these rules to my first example to see why it doesn't evaluate to true.


All the rules I quoted above are clearly stated here in the specs.

like image 42
doubleOrt Avatar answered Oct 10 '22 17:10

doubleOrt