Wat’s Happening : A Closer Look at Wat

I’m a big fan of Gary Bernhardt’s lightning “Wat” talk, from CodeMash 2012.

It helps that I’m something of a sucker for inside jokes. I’ve tried showing the video of the talk to my non-developer wife, who tolerates my raucous laughter with good grace and a bemused expression, despite not having the faintest idea why I find the content so amusing.

If you haven’t seen it, go watch it now — or the rest of this article won’t make any sense. It’s less than five minutes.

I’m a JavaScript developer, so I’m looking at Gary’s demo thinking to myself, “why does JavaScript do that? It’s a programming language, it follows defined rules. What is it doing to produce this unexpected behaviour?”

So I started to dig into a few of Gary’s examples to see if I could figure out what was going on.


[] + []

First, adding array literals. Gary shows that an array literal plus an array literal gives empty string. Wat? That doesn’t really make sense, and as Gary indicates we would more likely expect a Type Error, or perhaps an empty array. What’s JavaScript doing here?

> [] + []
''

Let’s talk about Java.

Java has operator overloading, where operators can have different implementations depending on their operands. This means the plus operator can do two different things: if the operands are number types, it will perform an addition operation — but if the operands are string types it performs a concatenation operation.

JavaScript does this too, but then trips over its own type system. Operator overloading works well in a strongly-typed language like Java, but JavaScript’s type coercion feature means it will attempt to normalise the types when figuring out which operator implementation to use.

Casting an empty array to a string just gives you an empty string. So if you try to add an array to an array, JavaScript knows that the plus operator makes no sense in the context of an array, so it casts each array to a string — where the plus operator does make sense — and then performs a string concatenation.

> [].toString();
''
> [].toString() + [].toString();
''

We can show this to be the case, by changing the toString method on the Array prototype. The default toString method for arrays casts each element of the array to a string, then concatenates those strings together with commas.

> ["foo", "bar"].toString();
'foo,bar'

Let’s replace the default implementation with one which transforms the array into a JSON representation of itself, and see what happens.

> Array.prototype.toString = function () {
return JSON.stringify(this);
};
> ["foo", "bar"].toString();
'["foo","bar"]'
> [] + []
'[][]'

Now that toString returns the JSON representation of the empty array, we can see more clearly what is happening. The array is being cast to a string, which is now its JSON equivalent, and those strings are being concatenated together.


[] + {}

Next, Gary tries to add an array to an object — again expecting a Type Error. Instead he seems to get back an object. This is what comes up on Gary’s screen:

> [] + {}
[object Object]

When I tried this in the node REPL, I see something slightly different:

> [] + {}
'[object Object]'

Gary describes this as an object, but I think he’s mistaken. Rather, this is a string containing the words “[object Object]

As before, JavaScript recognises that a plus operator doesn’t really make sense here, so casts the array to a string with the default toString method, resulting in an empty string.

It also casts the object literal to a string, but the default toString method on a JavaScript object is unfortunately unhelpful. It simply produces the string “[object Object]

> var o = {};
> o.toString();
'[object Object]'

When you add an array to an object, just as when you add an array to an array, JavaScript casts the operands to strings before performing a concatenation operation. In this case, an empty string is concatenated with [object Object] to produce [object Object].

Again, we can show what’s going on more clearly by changing the default toString implementation for both Arrays and Objects.

> Object.prototype.toString = function () {
return JSON.stringify(this);
};
> Array.prototype.toString = Object.prototype.toString;
> [] + {}
'[]{}'

{} + []

The next one is more interesting, and it took me a while to figure out what’s probably happening. I’m still not convinced I have the full picture, but anyway…

Gary flips the operands, arguing that this being plus, the argument order shouldn’t matter. Of course if this were an addition operation he would be right, but it’s not, it’s string concatenation. The string “foo” catted with the string “bar” is not the same thing as the string “bar” catted with the string “foo”. The order does make a difference here.

Even so, from what we’ve seen so far, you might expect the result to be “[object Object]” catted with empty string, producing “[object Object]” which is, in fact, the same result as before. But that isn’t what happens.

> {} + []
0

Wat?

I have an idea about what JavaScript is actually doing here, but I’m not 100% sure that I’m right. First, I need to talk about operator overloading again.

I mentioned that plus can do two things: string concatenation and addition. There is actually a third thing plus can represent, that being the unary plus operator. The unary plus operator attempts to cast its operand to a number.

> +"4"
4

When you apply a unary plus operator to an array, JavaScript first transforms the array into a scalar type by calling toString, and then casts the resulting string to a number.

> [].toString();
''
> Number('');
0

Applying this to an empty array first converts the array to an empty string, and then casts that empty string to Number 0. We can show this is happening again by changing the default toString method. In this case, we’ll change it to always return a constant string.

> Array.prototype.toString = function () {
return "2164";
};
> [].toString();
"2164"
> +[]
2164

Okay, so that’s the unary plus operator — back to the wat moment.

Why is JavaScript invoking the unary plus operator on the second operand instead of performing an addition or string concatenation? And how does that get us to 0 as the answer?

Let’s talk about code blocks. C-like languages have this concept of a code block, a related group of statements enclosed by braces. Usually when we use these blocks, they are prefixed by some other statement, for example an if condition, or a loop.

In many languages, including C and Java, the code block also defines a new variable scope. Variables within the block are visible to each other, but nothing outside the block can penetrate it. As such, it can be useful to define a “naked” code block, without any prefixed statement, simply to define a new scope.

Until recently, JavaScript didn’t have a concept of block scope; variables were scoped to the function which defined them. If you want to define a new scope, you would typically use an Immediately Invoked Function Expression. “Naked” code blocks didn’t make much sense, so JavaScript developers generally don’t use them, but they are supported.

> {} + []
0

Here is my idea: what we think is an object literal being catted to an array here is actually being interpreted by JavaScript as an empty code block, followed by a unary plus operator acting on an array. The empty code block does nothing, and the unary plus operator transforms the array into an empty string, and then Number 0.

We can show this is the case by using the new Object() notation in place of the object literal. When we do this, the result is the one we expected earlier — [object Object] — the result of concatenating the toString value of the object to the toString value of the array.

> new Object() + []
'[object Object]'

JavaScript is clearly not interpreting the braces as an object literal!


{} + {}

In the final example I’m going to talk about, Gary attempts to add two object literals, with the result being the special JavaScript number NaN.

> {} + {}
NaN

It should be clear, from the previous example, what is happening here. JavaScript is interpreting the braces as an empty code block, followed by a unary plus operator acting on an object literal.

As we know, the unary plus operator first casts the object to a string before casting to a number, but we also know an object literal cast to a string results in “[object Object].” That doesn’t parse as a number — ergo NaN.

> var s = {}.toString();
> console.log(s);
'[object Object]'
> Number(s);
NaN

However, I think there may be more to it than this. I decided to override the default toString method again to try and prove what was happening… but ended up with some unexpected results.

> Object.prototype.toString = function () {
return "5";
};
> {} + {}
'55'
> {} + 0
0
> 0 + {}
'05'
> Object.prototype.toString = function () {
return 5;
};
> {} + {}
10
> {} + 0
0
> 0 + {}
5
WAT
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.