about:benjie

Random learnings and other thoughts from an unashamed geek

Quantum JavaScript?

| Comments

TL;DR: In Chrome or Safari’s JavaScript console (or in jsc, but not node), run the following:

1
2
3
4
> {} + {}
NaN
> var a = {} + {}; a
'[object Object][object Object]'

a) Why is the first result wrong?
b) Why does storing it to a variable (observing it) change it?

(The answers are below.)


My friend Anton pointed out to me this short ‘WAT’ talk by Gary Bernhardt from 2012. It did something that confused me somewhat. Here’s what Gary’s example was:

1
2
3
4
5
6
7
8
9
$ jsc
> [] + [] // 1

> [] + {} // 2
[object Object]
> {} + [] // 3
0
> {} + {} // 4
NaN

Now 1 and 2 are fine, but 3 and 4 immediately went against what I was expecting - it truly was a ‘wat’ moment. I noticed was he was running this in JavaScriptCore, let’s see what Node.js makes of it:

1
2
3
4
5
6
7
8
9
$ node
> [] + [] // 1
''
> [] + {} // 2
'[object Object]'
> {} + [] // 3
'[object Object]'
> {} + {} // 4
'[object Object][object Object]'

Node.js seems to be doing exactly what I’d expect - using the + as string concatenation (since the leading argument is not numeric). So it’s calling .toString() on everything to produce the results you see above (an Array’s toString is effectively return this.join(","), and since it’s empty it just returns the empty string).

So what’s going on?! Let’s try V8 (the engine behind Node.js) in the browser (Chrome):

1
2
3
4
> {} + [] // 3
0
> {} + {} // 4
NaN

So… V8 in the browser agrees with JSC, not Node, and is behaving in this strange fashion. But wait!

1
2
3
4
> var a = {} + [];
undefined
> a
"[object Object]"

In the words of Gary: WAT?! By storing the value to a variable I’ve changed it’s value? This immediately reminds me of the Heisenberg Uncertainty Principle - that by observing something you implicitly change what it is; only in this case the observation is effectively storing it to a variable. Quantum JavaScript?

If you want to figure this out yourself, take a while to ponder this now before reading on…

Read on for the spoiler

Digging further, I realise that I could reproduce this by calling eval (which is basically what the E in REPL stands for). This allowed me to reproduce the same thing in Node:

1
2
3
4
5
6
7
8
> eval("[] + []")
''
> eval("[] + {}")
'[object Object]'
> eval("{} + []")
0
> eval("{} + {}")
NaN

At this pointed I cheated: I hopped on over to #javascript on irc.freenode.net and asked there. After some pondering, ckknight figured it out (with a little help from DDR_ and Havvy). Here’s the highlights:

DDR_     : Benjie: I think that {} is interpreted specially somehow in the console.
Havvy    : >> ({}) + ({})
ecmabot  : Havvy: (string) '[object Object][object Object]'
Havvy    : >> {}
ecmabot  : Havvy: undefined
ckknight : I get it
DDR_     : I'm guessing it's ambiguous or something.
ckknight : {} + {} turns into {}; (+{})
ckknight : since the first is interpreted as a block statement
ckknight : and then you have a unary + and an object

You can read the full transcript at the bottom of this post.

ckknight figured out that the first {} was being interpretted as a code block, rather than an object literal, and JavaScript’s automatic semicolon insertion (ASI) was taking over and changing the code to be interpretted like so:

1
2
3
4
> {}; +[] // 3
0
> {}; +{} // 4
NaN

The {}; is ignored (empty code block), and the leading + coerces the values to a number, which is equivalent to:

1
2
3
4
5
6
7
8
9
10
11
12
> Number([]) // 3
0
> Number([].toString())
0
> Number("")
0
> Number({}) // 4
NaN
> Number({}.toString())
NaN
> Number("[object Object]")
NaN

(Aside: to expand on the meaning of calling Number above, read about Number called as a function, ToNumber, ToPrimitive, and finally DefaultValue.)

So - mystery solved: storing to a variable gives the JavaScript interpretter enough context to interpret the first {} as an object literal and not a code block.

And I’m guessing that Node’s REPL, rather than calling plain eval("{} + {}"), does something a little more complex - perhaps like this:

1
eval.call(this, "(function(){return   {} + {}   }).call(this)"))

thereby (coincidentally?) side-stepping the issue.

IRC Transcript

Benjie   : When I type `{} + {}` into the javascript console (JSC or
         > Chrome inspector or Safari inspector; note: not node.js REPL) I get
         > `NaN` - why is this not the same as when I type `var a = {} + {}; a`
         > (which gives me `[object Object][object Object]`?
Benjie   : i.e. why does storing it to a variable somehow change it's
         > value. (Or more specifically, at a guess, how does the javascript
         > console coerce values and why isn't it the same as normal coercion?)
ckknight : Benjie: seems like an implementation flaw, like a bug seeped in
DDR_     : Benjie: I think that {} is interpreted specially somehow in the console.
Havvy    : >> var a = {} + {}; a
ecmabot  : Havvy: (string) '[object Object][object Object]'
Havvy    : >> {} + {}
ecmabot  : Havvy: (number) NaN
Havvy    : >> ({}) + ({})
ecmabot  : Havvy: (string) '[object Object][object Object]'
DDR_     : The second way is the correct (sob) way.
ckknight : >> {} + ({})
ecmabot  : ckknight: (number) NaN
ckknight : >> ({}) + {}
ecmabot  : ckknight: (string) '[object Object][object Object]'
ckknight : qwies
ckknight : weird*
Benjie   : ^_^
DDR_     : {} is undefined.
Havvy    : >> {}
ecmabot  : Havvy: undefined
DDR_     : In the console.
DDR_     : Heh, yes.
Havvy    : >> undefined + {}
ecmabot  : Havvy: (string) 'undefined[object Object]'
ckknight : DDR_: as an expression, yes
Havvy    : >> undefined + undefined
ecmabot  : Havvy: (number) NaN
ckknight : err
ckknight : as a statement, yes
ckknight : oh
ckknight : I get it
DDR_     : I'm guessing it's ambiguous or something.
DDR_     : I don't. :P
ckknight : {} + {} turns into {}; (+{})
Havvy    : {}; (+{})
Havvy    : >> {}; (+{})
ecmabot  : Havvy: (number) NaN
ckknight : >> {} + {}
ecmabot  : ckknight: (number) NaN
Havvy    : ckknight: Yeah, that could very well be it.
ckknight : since the first is interpreted as a block statement
ckknight : and then you have a unary + and an object
DDR_     : Ah.
Benjie   : ckknight: so the first {} is interpretted as a block
         > statement. Fascinating, I'm impressed you reached that conclusion so
         > quickly!

Comments