Blog

The Method behind the Madness

Inexplicably, JavaScript is not loved by every developer. Let's explore some dark alleys -- and show the haters how perfectly logical it all works.

Author image

Herbert Braun

Associate Director Frontend

As someone who mostly works with JavaScript I find it painful when people make fun of some of the language's well known peculiarities. I've been a web developer since when JavaScript was used only for simple UI animations and would not be touched by a "real" software engineer without gloves.

Still, I had to laugh about Gary Bernhardt's famous "Wat" presentation. Even if you are familiar with JS the results of connecting array and object literals with a plus operator are completely crazy. Let that sink in:

[] + [] === ''
[] + {} === '[object Object]'
{} + [] === 0
{} + {} === NaN

How could anybody in their right mind write a programming language like this? Or use it?

You probably saw this before. But have you ever thought about what's going on here?

Ambiguous

There are two forces at work that make the output almost unpredictable.

Like in most examples of JavaScript weirdness you see dynamic typing at work here. Basically, I like dynamic typing -- it makes the language smoother. I simply can write ${2 * number} is a number after assigning the value 1 to the variable without caring about the fact that I'm mixing numbers and strings here. Most of us will never have thought about the fine lines between Element, HTMLElement and Node until TypeScript screams at you for mixing things up -- JavaScript just silently, benevolently makes the code work.

You could, of course, also write number + number + ' is a number' which brings us to the second source of problems: the ambiguity of the plus operator, featuring both as mathematical plus and as string concatenation. So, if you use that operator the JavaScript compiler has to find out if the context is mathematical or stringy. One and one is two, except when it's eleven:

1 + 1 == 2
1 == '1'
1 + '1' == '11'

In the first of the four examples we combine two empty arrays with the plus operator. JavaScript has no problem transforming an array into a string: [1, 2] will silently become '[1,2]' when used in a string context. Responsible for that is the array prototype's toString() method which simply joins the array members with commas. If you have fun breaking things you can overwrite Array.prototype.toString and see for yourself!

So, [] + [] converts both arrays to empty strings and concatenates them, resulting in an empty string, too. Easy!

Object Strings?

Plain objects also have a stringification function -- Object.prototype.toString. The problem is that it's even less useful than the arrays' toString. Actually it is so bad that it's rather an error message than something anybody would want to use. Unlike Array.prototype.toString or JSON.stringify it doesn't even try to say something about the object content.

You can see it in action if you combine [] and the object literal {} by a plus operator. The stringification renders only a short description of the object: its type and the name of its prototype. Because plain objects are boring, both these values are "object" (with a capital O for the prototype). Stuff them into a pair of square brackets and you get [object Object] -- appended to the empty string representing the empty array.

++Ambiguous

So it's obvious that we get the same result if we turn the expression around to {} + [] because it doesn't matter if the empty string is at the start or at the end.

Well, not quite.

We talked about how the plus operator's ambiguity makes life more interesting for us JS developers but there's more where that came from. Are you really sure that {} is an object? Or is it ... a code block?

Okay, an empty code block makes even less sense than an empty object literal, yet in the absence of a preceding operator the compiler picks the former. This immediately leads to the next problem: the plus operator. How do you add or concatenate anything to a code block?

You can't: The code block has no value (not even undefined), so there is no way to use it with an operator that combines two values. Fortunately, the plus operator has a third meaning: It can also be a unary operator, a mathematical sign!

The array literal is not a number but it can be represented as an empty string. The unary plus is often used as a shortcut to parseInt() and that's how it works here. So the array goes from + [] to +'' to 0 -- and that's what we get from {} + [].

You can force the compiler to parse {} as an object literal by wrapping it into round braces. ({}) + [] indeed evaluates to '[object Object]', so the initial idea wasn't entirely wrong.

NaN is (not) not a number

I'm happy to say that the result of {} + {} is NaN because anything else would be a disappointment.

NaN is my favorite part of JavaScript because it hates itself so much that it doesn't want to be just "not a number". A rose is a rose, Infinity is Infinity, even the badly conceived and even worse implemented null can accept its own identity. But not NaN. Not even with the sloppy type-unsafe comparison: NaN != NaN. NaN is not something, it's not even nothing, it is not something. Imagine waking up and not being yourself, like Gregor Samsa in Kafka's "Metamorphosis" -- for NaN it's always been that way.

But I digress.

The two curly braces look deceptively similar but with the eyes of the JS compiler you spot the difference immediately: a code block left, an object literal right!

So we again have nothing followed by something which a unary plus operator desparately tries to convert into a number. Converting an array to a number was a stretch but converting a plain object is too much, even for mighty JavaScript: The intermediate string conversion results in '[object Object]', which is definitely ... Not a Number.

You can fix this issue easily with a small update that you should apply to all your projects:

Object.prototype.toString = () => 42;

Problem solved!

Trying hard to love JavaScript

Serious developers who work with Real Programming Languages often fail to understand why we frontenders do all the work in that awful miscreated toy language.

But the thing is that we can have these wonderfully puzzling edge cases without having to think a lot about them -- because if you would write {} + {} in production code I would question your mental sanity in a code review and if the dynamic typing falls on your feet you were sloppy in the first place (didn't I tell you to always use ===?). The vast majority of bugs I'm -producing- dealing with is much more boring than the exquisite features at work in the examples above and could be reproduced in most other programming languages.

Did I mention that the first version of JavaScript was written in six weeks by a single person?

Homework: Try guessing what happens if you replace the plus with a minus in the examples above.

Have fun!