Differentiate a block from an object initializer

2020-06-17 05:16发布

问题:

This is more a theoretical question than a practical one. It's about the parsing of some code delimited by curly braces.

Here are two examples of object initializers :

f({});
({a:3})

Here are two examples of blocks :

;{}
{a:3;}

In practice, it seems that {...} makes a block apart if the precedent code requires an expression.

But I've never seen such a rule explicit or made obvious in the ECMAScript specification and I'm not even sure it's true.

Is there a definitive non ambiguous reference somewhere ? A correct rule if this one isn't ?

回答1:

Is there a definitive non ambiguous reference somewhere ?

It's sort of spread all over the spec.

The short answer:

It depends on the context where the construct appears (which is why it's all over the spec). Probably the most specific place it's addressed is §12.4, where it says that an ExpressionStatement (an expression used where a statement is expected) can't start with a {.

The long answer:

The key is what the parser is expecting when it encounters the {: If it's expecting a statement, then it knows the { begins a block. But if it's expecting an expression, then it knows the { begins an object initializer. Let's look at an assignment:

doThis();   // This line is just for context
x = {a: 3};

At the beginning of the second line above, the parser is expecting a statement. But then it sees the x = and knows it's handling an assignment; at that point, after seeing the =, the parser is expecting an expression. A statement is invalid there. So it knows the { starts an object initializer, not a block.

In contrast:

doThis();   // This line is just for context
{a: 3};

The second line above is a block containing a labelled statement. (A very odd-looking one; we'll come back to that.) The parser knows that because at the beginning of that line, the parser is expecting a statement, not an expression.

There are lots of other places where the parser expects to see expressions, not statements. For instance, after the : in a property initializer:

obj = {
    prop: {a: 3}
};

...or within the arguments when doing a function call:

foo({a: 3});

...or after a unary operator, or just after an opening (, etc. In the spec, you can tell what the parser will be expecting by what it says in the grammar for what the parser is parsing, e.g. this syntax diagram from §12.5 defining the if statement:

 IfStatement :

if ( Expression ) Statement else Statement
if ( Expression ) Statement

That tells us that when handling an if statement, within the () the parser expects an expression, but after the if () bit, it expects a statement.

So far so good, but JavaScript allows (almost) any expression wherever a statement is allowed. This is valid, for instance:

doThis();   // This line is just for context
flag && doThat();

The second line above is a binary logical operator expression, but freestanding. The parser was expecting a statement when it encountered it. So the && expression is what the spec calls an ExpressionStatement. ExpressionStatement is defined by §12.4.

So that leaves us with some ambiguity: If the parser is expecting a statement, and sees a {, how does it know that's not the beginning of an object initializer expression acting as an ExpressionStatement?

The answer is: By fiat. :-) §12.4 says this when defining ExpressionStatement:

NOTE An ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block.

So there's no ambiguity, not because of some subtle syntax trick, but just because the spec says so. :-)

(If you had a reason for really, really wanting to use an object initializer expression as a statement, you can do it; just put it within ().)