Esoteric JavaScript
Everybody loves JavaScript type coercion, right? Who doesn’t?
And it is all too common knowledge that this following idiom evaluates to 0
{}+[]
// yields 0
But some time ago (over a decade ago), one of my coworkers pasted an interesting variation to this JavaScript idiom into our developer chat, and it immediately piqued my interest. It turns out that you can simply cast an array literal to a number by prepending it with a +
unary operator, and it will evaluate to 0
.
+[]
// yields 0
Interesting. This, immediately inspired me to take this concept a bit further, and turn it into a 1
+[1]
// yields 1
But that’s not fun. It’s sort of cheating, since we actually wrote a 1
there, so let’s try and do it differently. Maybe we can use the increment operator to turn that initial expression into a 1
?
++[]
// Throws an error
I guess not. It throws an error, since you cannot use an increment operator on an array.
How can we bypass that? Maybe hide the array inside of an array?
++[[]][0]
// yields 1
Voilà! We didn’t get an error!
And we now have an array literal that contains another array literal, to which we apply the increment operator on its first element, which is again, an array, but due to the increment operator it is first cast to a number (and an empty array is coerced to 0
naturally) and immediately incremented to a 1
, how about that!
Cool cool cool cool cool cool. But that 0
though. Let’s get rid of it. We already know how to write a zero in nonsense.js, right?
+[]
// yields zero
We just plug that into that increment expression and we get:
++[[]][+[]];
// yields 1
Hurray! We now have an obscure 1
!
We can also use the tilde operator
-(N+1)
to get the1
, but where’s the fun in that?
-~[]
// also yields 1, since ~0 is -1, and when we negate it it's 1
+!+[]
// also yields 1, since +[] is 0, !0 is true, and +true is 1
// but look how glorious this is!
++[[]][+[]];
With 0
and 1
we can now speak computerish! Through some bitwise math.
++[[]][+[]]<<++[[]][+[]];
// yields ... what?
// hint: we take a one and bitwise shift it left by one
If you’ve come this far without puking, it is time to congratulate yourself, and give yourself a good pat on the shoulder. Bonus pats if you solved the exercise above.
But it’s time to take this thing up to eleven.
With 0
and 1
, we can represent any number that we want, but having a 2
makes it even easier.
Let’s try and represent 72, with 1
s, 2
s and some bitwise math.
let a = 72;
// which is equivalent to:
a = Math.pow(2,6) + Math.pow(2,3); // = 2⁶ + 2³ = 64 + 8 = 72
// which is equivalent to:
a = (1 << 6) + (1 << 3);
// which is equivalent to:
a = (1<<((1<<2)+2)) + (1<<(2+1));
// a is 72
Since we already know how to write an obscure 1
and an obscure 2
, we can now write an obscure 72
, too!
let a = (1<<((2<<2)+2)) + (1<<(2+1));
// And since
// 1 is (++[[]][+[]]) and
// 2 is (++[[]][+[]]<<++[[]][+[]])
// we can rewrite a as
a = ((++[[]][+[]])<<(((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))+(++[[]][+[]]<<++[[]][+[]]))) + ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])+(++[[]][+[]])));
// a is *still* 72 :)
“Wow! That is incredible! How can you possible top this?” — I hear you say.
Well, imagine what would happen if you try to do something like this:
String.fromCharCode(72);
// yields "H"
I guess you’re seeing where I’m getting with this, by now ;)
As a final exercise (in futility), here’s my obscure “Hello world” code for you all to enjoy:
let msg=[
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]<<++[[]][+[]]))) + ((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))-(++[[]][+[]]))),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]<<++[[]][+[]]))) + ((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))+(++[[]][+[]]))) + ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]])) + (++[[]][+[]]),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))) - ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]])),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))) - ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]])),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))) - (++[[]][+[]]),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))+(++[[]][+[]]))),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))-(++[[]][+[]]))) - (++[[]][+[]]),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))) - (++[[]][+[]]) ,
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]])) - ((++[[]][+[]])<<(++[[]][+[]])),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]))) - ((++[[]][+[]])<<((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))) - ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]])),
((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))-(++[[]][+[]]<<++[[]][+[]]))) + ((++[[]][+[]])<<(((++[[]][+[]]<<++[[]][+[]])<<(++[[]][+[]]))+(++[[]][+[]]))) + ((++[[]][+[]])<<(++[[]][+[]]<<++[[]][+[]]))
].map(l=>String.fromCharCode(l)).join("");
alert(msg);
For those of you who find this unreadable, here’s a variant with the tilde operator:
let msg=[
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[]))+((-~[])<<(+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[])))))+((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[]))+((-~[])<<(+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[]))+((-~[])<<(+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[])))))+((-~[])<<(-~[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<((((-~[])<<(-~[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[]))),
(((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(-~[])))))+((-~[])<<((((-~[])<<(-~[]<<+!+[]))+((-~[])<<(+[])))))+((-~[])<<(-~[]<<+!+[])))
].map(l=>String.fromCharCode(l)).join("");
alert(msg);
Neat, right? Who knew JavaScript was Brainfuck all along?