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?
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?
[]
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...
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.
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:
- Let primValue be ToPrimitive(input argument, hint Number).
- 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.
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.
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)