为什么`(foo)=“bar”在JavaScript中合法?

时间:2020-12-19 22:28:25

In Node.js's REPL (tested in SpiderMonkey as well) the sequence

在Node.js的REPL中(也在SpiderMonkey中测试)序列

var foo = null;
(foo) = "bar";

is valid, with foo subsequently equal to "bar" as opposed to null.

是有效的,foo随后等于“bar”而不是null。

This seems counterintuitive because one would think the parenthesis would at least dereference bar and throw Invalid left-hand side in assignment`.

这似乎违反直觉,因为人们会认为括号至少会取消引用bar并在赋值中抛出无效的左侧。

Understandably, when you do anything interesting it does fail in aforementioned way.

可以理解的是,当你做任何有趣的事情时,它确实以上述方式失败了。

(foo, bar) = 4
(true ? bar : foo) = 4

According to ECMA-262 on LeftHandExpressions (so far as I can interpret) are no valid non-terminals that would lead to a parenthetical being accepted.

根据ECMA-262关于LeftHandExpressions(据我所知),没有有效的非终端会导致括号被接受。

Is there something I'm not seeing?

有没有我没见过的东西?

2 个解决方案

#1


28  

It's valid indeed. You're allowed to wrap any simple assignment target in parenthesis.

它确实有效。您可以在括号中包装任何简单的赋值目标。

The left hand part of the = operation is a LeftHandSideExpression as you correctly identified. This can be tracked down through the various precendence levels (NewExpression, MemberExpression) to a PrimaryExpression, which in turn might be a Cover­Parenthesized­Expression­And­Arrow­Parameter­List:

正确识别时,= operation的左手部分是LeftHandSideExpression。这可以通过各种优先级(NewExpression,MemberExpression)跟踪到PrimaryExpression,而PrimaryExpression又可能是CoverParenthesizedExpressionAndArrowParameterList:

( Expression[In, ?Yield])

(actually, when parsed with target PrimaryExpression, it's a ParenthesizedExpression).

(实际上,当使用目标PrimaryExpression进行解析时,它是一个ParenthesizedExpression)。

So it's valid by the grammar, at least. Whether it's actually valid JS syntax is determined by another factor: early error static semantics. Those are basically prose or algorithmic rules that make some production expansions invalid (syntax errors) in certain cases. This for example allowed the authors to reuse the array and object initialiser grammars for destructuring, but only applying certain rules. In the early errors for assignment expressions we find

所以它至少在语法上是有效的。它是否真正有效的JS语法是由另一个因素决定的:早期错误静态语义。这些基本上是散文或算法规则,在某些情况下使某些生产扩展无效(语法错误)。例如,这允许作者重用数组和对象初始化语法进行解构,但仅应用某些规则。在我们发现的赋值表达式的早期错误中

It is an early Reference Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget of LeftHandSideExpression is false.

如果LeftHandSideExpression既不是ObjectLiteral也不是ArrayLiteral并且LeftHandSideExpression的IsValidSimpleAssignmentTarget为false,则它是早期的参考错误。

We can also see this distinction in the evaluation of assignment expressions, where simple assignment targets are evaluated to a reference that can be assigned to, instead of getting the destructuring pattern stuff like object and array literals.

我们还可以在赋值表达式的求值中看到这种区别,其中简单赋值目标被计算为可以赋值的引用,而不是获取像对象和数组文字这样的解构模式。

So what does that IsValidSimpleAssignmentTarget do to LeftHandSideExpressions? Basically it allows assignments to property accesses and disallows assignments to call expressions. It doesn't state anything about plain PrimaryExpressions which have their own IsValidSimpleAssignmentTarget rule. All it does is to extract the Expression between the parentheses through the Covered­Parenthesized­Expression operation, and then again check IsValidSimpleAssignmentTarget of that. In short: (…) = … is valid when … = … is valid. It'll yield true only for Identifiers (like in your example) and properties.

那么IsValidSimpleAssignmentTarget对LeftHandSideExpressions做了什么?基本上,它允许分配属性访问,并禁止分配调用表达式。它没有说明具有自己的IsValidSimpleAssignmentTarget规则的普通PrimaryExpressions的任何内容。它所做的只是通过CoveredParenthesizedExpression操作提取括号中的Expression,然后再次检查IsValidSimpleAssignmentTarget。简而言之:(...)= ...在...... = ...有效时有效。它仅对标识符(如您的示例)和属性产生真实。

#2


15  

As per @dlatikay's suggestion, following an existing hunch, research into CoveredParenthesizedExpression yielded a better understanding of what's happening here.

根据@ dlatikay的建议,在现有的预感之后,对CoveredParenthesizedExpression的研究使人们更好地理解了这里发生的事情。

Apparently, the reason why a non-terminal cannot be found in the spec, to explain why (foo) is acceptable as a LeftHandExpression, is surprisingly simple. I'm assuming you understand how parsers work, and that they operate in two separate stages: Lexing and Parsing.

显然,在规范中找不到非终端的原因,解释为什么(foo)作为LeftHandExpression是可接受的,这简直太简单了。我假设您了解解析器的工作原理,并且它们分两个阶段运行:Lexing和Parsing。

What I've learned from this little research tangent is that the construct (foo) is not technically being delivered to parser, and therefor the engine, as you might think.

我从这个小小的研究中得到的结论是,构造(foo)在技术上并没有被提供给解析器,因此你可能会想到引擎。

Consider the following

var foo = (((bar)));

As we all know, something like this is perfectly legal. Why? Well you visually can just ignore the parenthesis while the statement remains making perfect sense.

众所周知,这样的事情是完全合法的。为什么?那么你在视觉上可以忽略括号,而声明仍然是完美的意义。

Similarly, here is another valid example, even from a human readability perspective, because the parentheses only explicate what PEMDAS already makes implicit.

同样,这是另一个有效的例子,即使从人类可读性的角度来看也是如此,因为括号只能说明PEMDAS已经隐含的内容。

(3 + ((4 * 5) / 2)) === 3 + 4 * 5 / 2
>> true

One key observation can be loosely derived from this, given an understanding of how parsers already work. (remember, Javascript still is being parsed (read: compiled) and then run) So in a direct sense, these parentheses are "stating the obvious".

考虑到解析器已经如何工作,可以从中松散地得出一个关键的观察结果。 (记住,Javascript仍然被解析(读取:编译)然后运行)所以从直接意义上讲,这些括号是“陈述明显的”。

So all that being said, what exactly is going on?

Basically, parentheses (with the exception of function parameters) are collapsed into proper groupings of their containing symbols. IANAL but, in lay man's terms, that means that parentheses are only interpreted to guide the parser how to group what it reads. If the context of the parentheses is already "in order", and thus does not require any tweaking of the emitted AST, then the (machine) code is emitted as if those parentheses did not exist at all.

基本上,括号(函数参数除外)会折叠成包含其符号的正确分组。 IANAL,但是,用非专业人的术语来说,这意味着括号仅被解释为指导解析器如何对其读取的内容进行分组。如果括号的上下文已经“按顺序”,因此不需要对发出的AST进行任何调整,则发出(机器)代码,好像这些括号根本不存在。

The parser is more or less being lazy, assuming the parens are impertinent. (which in this edge-case, is not true)

假设parens是无关紧要的,解析器或多或少是懒惰的。 (在这个边缘情况下,不是真的)

Okay, and where exactly is this happening?

According to 12.2.1.5 Static Semantics: IsValidSimpleAssignmentTarget in the spec,

根据12.2.1.5静态语义:规范中的IsValidSimpleAssignmentTarget,

PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)

PrimaryExpression:(CoverParenthesizedExpressionAndArrowParameterList)

  1. Let expr be CoveredParenthesizedExpression of CoverParenthesizedExpressionAndArrowParameterList.
  2. 设expr为CoverParenthesizedExpressionAndArrowParameterList的CoveredParenthesizedExpression。
  3. Return IsValidSimpleAssignmentTarget of expr.
  4. 返回expr的IsValidSimpleAssignmentTarget。

I.E. If expecting primaryExpression return whatever is inside the parenthesis and use that.

I.E.如果期望primaryExpression返回括号内的任何内容并使用它。

Because of that, in this scenario, it does not convert (foo) into CoveredParenthesizedExpression{ inner: "foo" }, it converts it simply into foo which preserves the fact that it is an Identifier and thus syntactically, while not necessarily lexically, valid.

因此,在这种情况下,它不会将(foo)转换为CoveredParenthesizedExpression {inner:“foo”},它将其简单地转换为foo,这保留了它是一个标识符的事实,因此在语法上,而不一定是词法上,有效的。

TL;DR

It's wat.

这是wat。

Want a little more insight?

想要更多洞察力?

Check out @Bergi's answer.

查看@ Bergi的答案。

#1


28  

It's valid indeed. You're allowed to wrap any simple assignment target in parenthesis.

它确实有效。您可以在括号中包装任何简单的赋值目标。

The left hand part of the = operation is a LeftHandSideExpression as you correctly identified. This can be tracked down through the various precendence levels (NewExpression, MemberExpression) to a PrimaryExpression, which in turn might be a Cover­Parenthesized­Expression­And­Arrow­Parameter­List:

正确识别时,= operation的左手部分是LeftHandSideExpression。这可以通过各种优先级(NewExpression,MemberExpression)跟踪到PrimaryExpression,而PrimaryExpression又可能是CoverParenthesizedExpressionAndArrowParameterList:

( Expression[In, ?Yield])

(actually, when parsed with target PrimaryExpression, it's a ParenthesizedExpression).

(实际上,当使用目标PrimaryExpression进行解析时,它是一个ParenthesizedExpression)。

So it's valid by the grammar, at least. Whether it's actually valid JS syntax is determined by another factor: early error static semantics. Those are basically prose or algorithmic rules that make some production expansions invalid (syntax errors) in certain cases. This for example allowed the authors to reuse the array and object initialiser grammars for destructuring, but only applying certain rules. In the early errors for assignment expressions we find

所以它至少在语法上是有效的。它是否真正有效的JS语法是由另一个因素决定的:早期错误静态语义。这些基本上是散文或算法规则,在某些情况下使某些生产扩展无效(语法错误)。例如,这允许作者重用数组和对象初始化语法进行解构,但仅应用某些规则。在我们发现的赋值表达式的早期错误中

It is an early Reference Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget of LeftHandSideExpression is false.

如果LeftHandSideExpression既不是ObjectLiteral也不是ArrayLiteral并且LeftHandSideExpression的IsValidSimpleAssignmentTarget为false,则它是早期的参考错误。

We can also see this distinction in the evaluation of assignment expressions, where simple assignment targets are evaluated to a reference that can be assigned to, instead of getting the destructuring pattern stuff like object and array literals.

我们还可以在赋值表达式的求值中看到这种区别,其中简单赋值目标被计算为可以赋值的引用,而不是获取像对象和数组文字这样的解构模式。

So what does that IsValidSimpleAssignmentTarget do to LeftHandSideExpressions? Basically it allows assignments to property accesses and disallows assignments to call expressions. It doesn't state anything about plain PrimaryExpressions which have their own IsValidSimpleAssignmentTarget rule. All it does is to extract the Expression between the parentheses through the Covered­Parenthesized­Expression operation, and then again check IsValidSimpleAssignmentTarget of that. In short: (…) = … is valid when … = … is valid. It'll yield true only for Identifiers (like in your example) and properties.

那么IsValidSimpleAssignmentTarget对LeftHandSideExpressions做了什么?基本上,它允许分配属性访问,并禁止分配调用表达式。它没有说明具有自己的IsValidSimpleAssignmentTarget规则的普通PrimaryExpressions的任何内容。它所做的只是通过CoveredParenthesizedExpression操作提取括号中的Expression,然后再次检查IsValidSimpleAssignmentTarget。简而言之:(...)= ...在...... = ...有效时有效。它仅对标识符(如您的示例)和属性产生真实。

#2


15  

As per @dlatikay's suggestion, following an existing hunch, research into CoveredParenthesizedExpression yielded a better understanding of what's happening here.

根据@ dlatikay的建议,在现有的预感之后,对CoveredParenthesizedExpression的研究使人们更好地理解了这里发生的事情。

Apparently, the reason why a non-terminal cannot be found in the spec, to explain why (foo) is acceptable as a LeftHandExpression, is surprisingly simple. I'm assuming you understand how parsers work, and that they operate in two separate stages: Lexing and Parsing.

显然,在规范中找不到非终端的原因,解释为什么(foo)作为LeftHandExpression是可接受的,这简直太简单了。我假设您了解解析器的工作原理,并且它们分两个阶段运行:Lexing和Parsing。

What I've learned from this little research tangent is that the construct (foo) is not technically being delivered to parser, and therefor the engine, as you might think.

我从这个小小的研究中得到的结论是,构造(foo)在技术上并没有被提供给解析器,因此你可能会想到引擎。

Consider the following

var foo = (((bar)));

As we all know, something like this is perfectly legal. Why? Well you visually can just ignore the parenthesis while the statement remains making perfect sense.

众所周知,这样的事情是完全合法的。为什么?那么你在视觉上可以忽略括号,而声明仍然是完美的意义。

Similarly, here is another valid example, even from a human readability perspective, because the parentheses only explicate what PEMDAS already makes implicit.

同样,这是另一个有效的例子,即使从人类可读性的角度来看也是如此,因为括号只能说明PEMDAS已经隐含的内容。

(3 + ((4 * 5) / 2)) === 3 + 4 * 5 / 2
>> true

One key observation can be loosely derived from this, given an understanding of how parsers already work. (remember, Javascript still is being parsed (read: compiled) and then run) So in a direct sense, these parentheses are "stating the obvious".

考虑到解析器已经如何工作,可以从中松散地得出一个关键的观察结果。 (记住,Javascript仍然被解析(读取:编译)然后运行)所以从直接意义上讲,这些括号是“陈述明显的”。

So all that being said, what exactly is going on?

Basically, parentheses (with the exception of function parameters) are collapsed into proper groupings of their containing symbols. IANAL but, in lay man's terms, that means that parentheses are only interpreted to guide the parser how to group what it reads. If the context of the parentheses is already "in order", and thus does not require any tweaking of the emitted AST, then the (machine) code is emitted as if those parentheses did not exist at all.

基本上,括号(函数参数除外)会折叠成包含其符号的正确分组。 IANAL,但是,用非专业人的术语来说,这意味着括号仅被解释为指导解析器如何对其读取的内容进行分组。如果括号的上下文已经“按顺序”,因此不需要对发出的AST进行任何调整,则发出(机器)代码,好像这些括号根本不存在。

The parser is more or less being lazy, assuming the parens are impertinent. (which in this edge-case, is not true)

假设parens是无关紧要的,解析器或多或少是懒惰的。 (在这个边缘情况下,不是真的)

Okay, and where exactly is this happening?

According to 12.2.1.5 Static Semantics: IsValidSimpleAssignmentTarget in the spec,

根据12.2.1.5静态语义:规范中的IsValidSimpleAssignmentTarget,

PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)

PrimaryExpression:(CoverParenthesizedExpressionAndArrowParameterList)

  1. Let expr be CoveredParenthesizedExpression of CoverParenthesizedExpressionAndArrowParameterList.
  2. 设expr为CoverParenthesizedExpressionAndArrowParameterList的CoveredParenthesizedExpression。
  3. Return IsValidSimpleAssignmentTarget of expr.
  4. 返回expr的IsValidSimpleAssignmentTarget。

I.E. If expecting primaryExpression return whatever is inside the parenthesis and use that.

I.E.如果期望primaryExpression返回括号内的任何内容并使用它。

Because of that, in this scenario, it does not convert (foo) into CoveredParenthesizedExpression{ inner: "foo" }, it converts it simply into foo which preserves the fact that it is an Identifier and thus syntactically, while not necessarily lexically, valid.

因此,在这种情况下,它不会将(foo)转换为CoveredParenthesizedExpression {inner:“foo”},它将其简单地转换为foo,这保留了它是一个标识符的事实,因此在语法上,而不一定是词法上,有效的。

TL;DR

It's wat.

这是wat。

Want a little more insight?

想要更多洞察力?

Check out @Bergi's answer.

查看@ Bergi的答案。