8

I've come across a working JavaScript code that I can't explain. For example:

  • +[]===0
  • -[]===0
  • ~[]===-1
  • ~-~[]===-2
  • ~-~-~-~-~[]===-5
  • ~-~-~-~-~[]+~[]===-6
  • ~+~[]===0
  • ~+~+~[]===-1
  • ~+~+~+~[]===0

Can you explain the logic of these expressions?

Martin.
  • 10,494
  • 3
  • 42
  • 68
Denis
  • 101
  • 3

5 Answers5

11

[] is an empty array object, so:

+[]: force empty array to be positive integer, aka 0, which is === to 0
-[]: force empty array to be negative integer, aka 0, which is === to 0
~[]: bitwise NOT empty array, which evaluates to -1, which is === to -1
~-~[]: bitwise NOT of negated NOTted empty array: ~-(-1) -> ~1 -> -2

etc...

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • 1
    should be: `~1 -> -2` on the last string – Denis Nov 23 '11 at 17:35
  • -1 `+[]: force empty array to be positive integer, aka 0, which is === to 0` doesn't explain why it happens. It just restates the result. Obviously if `+[] === 0`, then `+[]` must have been converted to `0`. The question asks to explain the logic. – RightSaidFred Nov 23 '11 at 20:11
2

When you use the + or - operator on anything, it calls Number on it. Number([]) returns 0, so you get your first two answers.

The ~ operator is bitwise NOT. Basically it inverses all the bits in a number, which changes 0 to -1. More on bitwise operators here.

The rest are just combinations of those cases. You can pretty much combine those things to make any number you want.

Alex Turpin
  • 46,743
  • 23
  • 113
  • 145
2

Instead of just restating the result you demonstrated in the question, I'll try to give a description of why you get that result.

Explanation

Taking only the first example (because the rest will do something similar), we can follow the specification to see what happens.

From 11.4.6 Unary + Operator, we can see that a ToNumber conversion takes place.

Return ToNumber(GetValue(expr)).

From 9.3 ToNumber we see that if given an Object (like your Array), a ToPrimitive conversion takes place followed by another attempt at ToNumber.

Object

Apply the following steps:

  1. Let primValue be ToPrimitive(input argument, hint Number).
  2. Return ToNumber(primValue).

From 9.1 To Primitive, if ToPrimitive gets an Object, we can see that its [[DefaultValue]] is fetched:

Object

Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

From 8.12.8 [[DefaultValue]] (hint), what will ultimately happen will be that toString() will be called on the Array, and returned. This string gets sent to the recursive ToNumber as described above.

So what happens when ToNumber conversion is done on a String? Well it is described in 9.3.1 ToNumber Applied to the String Type, and is a bit lengthy. Simpler is to just do the conversion directly, and see what happens:

Number("");     // result is 0 on an empty string
Number("    "); // result is 0 on a string with only whitespace
Number("123");  // result is 123 on a numeric string
Number(" 123 ");// result is 123 on a numeric string with leading & trailing spaces
Number("abc");  // result is NaN (not a number) on non-numeric strings

So the question is, what string do we get back from our Array. Again, this is easy to simply test.

[].toString();  // result is "" (empty string)

Since the result is an empty string, and a ToNumber conversion of an empty string is 0 as shown above, that would mean we are comparing 0 === 0.

It would be the same as if we did:

Number( [].toString() ) === 0; // true

Or to draw it out a bit more:

var x = [];
x = x.toString();  // ""
x = Number( x );   // 0
x === 0;  // true

More toString results.

To show more toString conversions of Arrays, consider the following:

[1].toString();            // "1"
[1,2,3].toString();        // "1,2,3"
["a",1,"b",2].toString();  // "a,1,b,2"

So to do a ToNumber conversion on the above Arrays, the first would give us a number, and the last two would result in NaN.

Number([1]);            // 1
Number([1,2,3]);        // NaN
Number(["a",1,"b",2]);  // NaN

Some proof

To provide further proof that this toString() conversion happens before the ToNumber conversion, we can actually modify Array.prototype.toString to provide a different result, and any ToNumber conversions will use that modified result.

Array.prototype.toString = function() {
    var n = 0;
    for( var i = 0; i < this.length; i++ ) {
        n += this[i];
    }
    return n;
};

Here I've replaced the toString on Array.prototype with a function that sums the Array. Obviously you don't want to do this, but we can show how we will now get a different result.

Number([1,2,3]);   // 6
+[1,2,3];          // 6

So now you can see that the ToNumber conversion of our Array that would previously have resulted in NaN is now resulting in the sum of the items in the Array.

RightSaidFred
  • 11,209
  • 35
  • 35
1

I'll do my best:

[]===0 is of course false because [] is not exactly equal to 0. However, []==0 is true because an implicit cast exists.

+ and -[] work because the plus or minus casts [] to a real number.

~0 (the bitwise inverse of 0) is -1. Thus ~[]===-1 works.

The others work just by subtracting or adding -1 a bunch of times.

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
0

I believe, correct me if I'm wrong, but adding to an array (as in, adding a value to an array rather than adding a value into an array) casts it to a number. The rest is just using basic +, - operators (the tilde (~) is a bitwise NOT) to modify the number and then an equation.

So [] == array ([]);
[] + 1 == number (0);
+[]===0 (true)
n0wak
  • 76
  • 3