145

I noticed today that Chrome 49 no longer outputs NaN when you type {}+{} into the console. Instead it outputs the string [object Object][object Object].

Why is this? Did the language change?

Filip Haglund
  • 13,919
  • 13
  • 64
  • 113
  • Try `{}+{}+1`, and you'll see `NaN` again, while `({} + {}) + 1` yields `"[object Object][object Object]1"`. – Hypaethral Apr 05 '16 at 22:18
  • 13
    looks like chrome now treats this operation as a string concat rather than addition. WHY that is, i don't know, which is why this is a comment not an answer :) try `var e = {}; e.toString()` and you'll see what i mean – user428517 Apr 05 '16 at 22:18
  • it's all about type coercion (in extreme cases) .... BTW type `+{}` still return NaN – maioman Apr 05 '16 at 22:27
  • 19
    *"Did the language change?"* No. – Felix Kling Apr 05 '16 at 22:48
  • 6
    @FelixKling *Will* the language change? ...no. :c – cat Apr 06 '16 at 00:00
  • 1
    Note that `eval('{}+{}')` and `eval('({}+{})')` continue to return the expected results of `NaN` and `"[object Object][object Object]"` respectively. No, the language did not change. – zzzzBov Apr 06 '16 at 03:00
  • 18
    Maybe [WATMAN](https://www.destroyallsoftware.com/talks/wat) had something to do with it? – rickster Apr 06 '16 at 03:49
  • 1
    @rickster that's how I found it. I was recreating that for a presentation. – Filip Haglund Apr 08 '16 at 09:35

3 Answers3

153

Chrome devtools now automatically wrap everything that begins with { and ends with } in an implicit pair of parentheses (see code), to force its evaluation as an expression. That way, {} creates an empty object now. You can see this if you go back through the history (), the previous line will be contained in (…).

Why? I don't know, but I could guess it reduces confusion for newbies that don't know of the block-vs-object-literal thing, and it's also more helpful if you just want to evaluate an expression.

And in fact that's the reasoning, as discussed in bug 499864. Pure convenience. And because node REPL had it as well (see code).

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 183
    Stupid Chrome, `{a:1}),({b:2}` should throw an error, not produce an object. – Oriol Apr 05 '16 at 22:42
  • 30
    That's what happens when you parse arbitrarily deep nested structures with regex https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags – Filip Haglund Apr 05 '16 at 23:13
  • 4
    I have no idea why, but somehow when I see my messages there I feel "famous" although that page is just as public as this one :D Weird StackOverflow problem. Here's my older answer about the problem http://stackoverflow.com/questions/17268468/why-is-nan-only-on-the-client-side-why-not-in-node-js/17269376#17269376 – Benjamin Gruenbaum Apr 06 '16 at 07:03
  • 3
    I don't like the current implementation and plan to fix it. https://bugs.chromium.org/p/chromium/issues/detail?id=499864#c17 – Zirak Apr 06 '16 at 18:54
  • 1
    @Zirak Good luck fixing that rubbish, IMO it should be backed out ASAP. But if you want to improve it, consider adding a newline before the inserted `)` in case it's in a comment, e.g. `{a:3} // :-}` might still produce an object. – Oriol Apr 07 '16 at 10:08
  • fails: {let i=0;var increment=()=>i++} ok: ;{let i=0;var increment=()=i++} can the js parser just parse js and not try to be smart!!! – James Wakefield Oct 17 '16 at 04:33
44

If you hit the up arrow after checking this, you'll notice that instead of {} + {} it displays ({} + {}), which results in "[object Object][object Object]".

In comparison, in Firefox, {} + {} still displays NaN, but if you do ({} + {}) it also displays "[object Object][object Object]".

So, it looks like Chrome is adding the surrounding parenthesis automatically when it sees this operation.

J. Titus
  • 9,535
  • 1
  • 32
  • 45
  • 22
    this answer is correct. but wow, man, i'm not sure i like that chrome does that. bad google. – user428517 Apr 05 '16 at 22:24
  • 1
    @sgroves I'd be interested to see if this is the same in Canary, and if it was done on purpose or is actually a bug. – J. Titus Apr 05 '16 at 22:49
  • 8
    `{} + {}` when not "sanitized" to `({} + {})` is treated as `+ {}` because `{}` is parsed as an empty block. – Greg Nisbet Apr 05 '16 at 23:27
  • Same behavior in Canary, which makes sense according to Bergi's answer. – J. Titus Apr 05 '16 at 23:43
  • 7
    Why would it return NaN in the first place? – David G Apr 06 '16 at 01:36
  • 26
    @0x499602D2: Because unless you do the parens (or otherwise cause the parser to shift into expecting an expression rather than statement), the initial `{}` is just an empty code block and is disregarded, leaving us with `+{}`, which is a unary `+` and an empty object initializer. `+` will coerce its argument to number, which involves converting the object to a primitive (which will end up being a `toString` in this case, resulting in `"[object Object]"`), and so we get `+"[object Object]"` which is `NaN` because `"[object Object]"` cannot be converted to a valid number. – T.J. Crowder Apr 06 '16 at 08:49
4

As of Chrome 54 with regards to the console:

-"I converted that block to an Object for you" -Clippy Unfortunately, I added the Clippy quote myself. The console gives no information about what it has done for you.

The new rules are incredibly simple saving us the trouble of laboriously typing these 2 difficult charcters o= or 0, before pasting Object Literals into the console:

  • If you have code that starts with: optional whitespace,(no comments permitted) followed by a {;
  • and that code could be interpreted as an object;
  • and that object is followed by no other code, unless:
  • the code after the first object is a binary operator,
  • then there can be as many operations as you like including groupings
  • provided the final operator has an Object literal in the right hand position;
  • and that final Object has not been grouped in parens
  • and that code is not terminated with a semicolon
  • and there are no comments following the code (internal comments are permitted so long as they are not in the initial or final position)
  • then and only then will your JavaScript (which may or may not actually be valid code) will be re-intrepted as a valid Object. You will not be informed that your code has been reinterpreted.

{wat:1}),({wat:2} Is finally an error again.

{let i=0;var increment=_=>i++} is correctly allowed, finally, which is quite a nice way of doing closures.

However, the following is incorrectly an object, this is just as a convenience as mentioned by @Bergi, it interprets JS wrong to help you! The spec says it is a block with a labeled statement "foo" with a literal 1 that is not assigned to anything.

{foo:1}

The above should be the same as

if(1) {
    foo: 1
}

The following is treated correctly as a block... because it has a comment in front of it!

//magic comment
{foo:1}

So is this:

{foo:1}
//also magic

This is an Object:

{foo:
//not so magic comment
1}

This is an error

//not so magic comment
{foo:1}.foo

So is this:

{foo:1}.foo

This is fine:

1..wat

undefined

so is this:

['foo'][0]

The next one is correctly interpreted as an object whacked into the expression position with a 0, which is generally how we unambiguously ensure we have an expression instead of a statement.

0,{foo:1}.foo

I don't get why they wrap the value in parens. JS has some ridiculous design decisions, but trying to make it behave nicer in this one situation isn't really an option, the console needs to run JS correctly, and we need to be confident that chrome isn't just guessing that it thinks we really meant it to do something else.

If you don't like comma operators you can use assignment

x = {foo:1}.foo

Because as it stands

{} + {} + {}

"[object Object][object Object][object Object]"

;{} + {} + {}

"NaN[object Object]"

Crazy and consistent I can deal with... crazy and inconsistent no thank you!

Community
  • 1
  • 1
James Wakefield
  • 526
  • 3
  • 11
  • a REPL is not the language it's a REPL. It passes strings to the language **among other things**. [Here's several things the Chrome REPL does the language itself does not](https://developers.google.com/web/tools/chrome-devtools/console/utilities). They're pretty useful so I'm really glad they didn't stick with just the plain language. – gman Dec 04 '19 at 16:14
  • @gman A REPL Reads a string, Evaluates it, Prints the results and then prepares to read the next piece of dynamic code. Nothing in the linked page was invalid JavaScript. The "$_ " variable scoped to the console context is clearly a convenience that only makes sense in a REPL. Nonetheless, "$_" is a valid variable name, the rest are just normal functions and classes invoked with normal JavaScript. – James Wakefield Jan 14 '20 at 06:20
  • Not sure what your point is. My point is the language is one thing, the environment it runs in is another. You gave an example in your answer. In JS `{foo:1}` and `{foo:1}//` produce the same thing. In the Chrome JS REPL they don't. The REPL is doing more than just evaluating JS. It's processing the strings and deciding to to different things. – gman Jan 14 '20 at 06:39
  • `var x = eval('{a:1}')` In valid JavaScript x is now 1, not the more intuitive object {a:1}. Yes, that's weird, but you can't just change the language because it does weird things. Everything other than JSON strings are interpreted as JavaScript and evaluated. Typing `0,` before pasting the JSON isn't difficult, alternatively I'd be happy with a warning that the string was interpreted as an object instead of JavaScript for convenience. – James Wakefield Jan 14 '20 at 07:10