[ /js/es6-wat-ii ]

Earlier today I wrote a little article about something weird in ECMA6's arrow syntax.

I sat down and started screwing around again, and ran across something I was wondering about. I decided to look into it a little more, and it just keeps on going deeper.

In the previous article I covered how arrow syntax fools you into thinking any expression in your arrow syntax will be returned. Despite the fact that an object literal is a valid expression, it doesn't get returned, because in these circumstances the curly braces create a function block, not an object.

As such, [1,2,3].map(() => {}); is equivalent to [1,2,3].map(function () {});. This is counter-intuitive for me, but I understand why they made this decision.

Now, with that in mind, I wondered what would happen if I made it more obvious that it was an object, by putting some attributes inside it.

var A = [1, 2, 3];

A.map((x) => { pew: 'pew' });

My expection was that it would recognize that attributes don't belong in a function block, and that it would complain about a syntax error, however:

> [1,2,3].map(() => { pew: 'pew' });
[ undefined, undefined, undefined ]

The 'pew' attribute just gets silently eaten, and the function returns undefined.

Trying the same in ECMA <= 5 syntax

[1, 2, 3].map(function () { pew: 'pew' });
[ undefined, undefined, undefined ]

Ok, so, from that we can see that attributes are legal in functions. I didn't know that.

This made me curious, can we try to access that attribute?

var f = function () {
    pew: 'pew'
};

> f.pew
undefined

Nope.

Can we use this kind of syntax in any other blocks?

if (true) { pew: 'pew' }
'pew' // printed

WAT

So, something that resembles an object property in an if block returns the value of the property in the block if it executes. We can confirm that with:

if (false) { pew: 'pew' }
undefined // returned

So, something weird is definitely going on.

What if we have more than one property?

if (true) { bang: 'bang', pew: 'pew' } // this isn't valid

if (true) { bang: 'bang'; pew: 'pew' } // this **is** valid
'pew' // and it returns the last attribute 

var x = if (true) { bang: 'pew' } // invalid syntax

So, in fact, though you see the property being returned at the repl, it's not really accessible in any meaningful way from outside an if statement.

Conclusion

Now I'm really confused. I still don't know why any of this is valid Javascript. It doesn't seem like it should even parse to me, but it does.

I'm trying to think of how it can actually be used, but even if there is a use case for it, it would probably be a really bad idea because nobody is going to be able to read it (I certainly can't).

I'm going to keep searching, but if anyone else can explain this to me, I'd love to have some answers.

--ansuz

2016.02.19

Continued information

ralphtheninja read my blag and pointed me towards labeled statements.

Julian Gruber made the same observation, and let me know by email. Thanks for reading and taking the time to contact me!

Suggested best practices

UPDATE: Having talked to a few more people, we've all agreed that due to the syntactic similarity of object literals and arrow syntax blocks with labels inside, and the relative obscurity of labels in Javascript great care should be taken to avoid leaving any ambiguity in arrow syntax:

[1, 2, 3].map((x, i) => ({
    x: x * x,
    i: i
}));

// results in:
// [ { x: 1, i: 0 }, { x: 4, i: 1 }, { x: 9, i: 2 } ]
// as expected

So, if you want to return the result of an expression, you probably want to wrap it up in parens. It only really matters when that expression is an object, but if you're in the habit of doing it, you won't ever run into this edge case.

For all I know, there might be a few more of these rarely used syntaxes lurking in the dark corners of the language, just waiting to screw me over.

Happy coding && Play safe!

--ansuz

2016-02-22