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

Why is `(foo) = "bar"` legal in JavaScript?

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

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

有效,foo 随后等于 "bar" 而不是 null.

这似乎违反直觉,因为人们会认为括号至少会取消引用 bar 并抛出 Invalid left-hand side in assignment`.

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

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

根据 ECMA-262 on LeftHandExpressions(据我所知)没有有效的非终结符会导致括号被接受。

有什么我没看到的吗?

根据 , following an existing hunch, research into CoveredParenthesizedExpression 可以更好地理解这里发生的事情。

显然,在 spec 中找不到非终结符的原因要解释为什么 (foo) 可以作为 LeftHandExpression 接受是非常简单的。我假设您了解解析器的工作原理,并且它们在两个不同的阶段运行:LexingParsing.

我从这个小小的研究切线中学到的是,构造 (foo) 在技术上并没有像您想象的那样被传递给解析器,因此也没有被传递给引擎。

Consider the following

var foo = (((bar)));

众所周知,这样的事情是完全合法的。为什么?好吧,您在视觉上可以 忽略 括号,而该语句仍然非常有意义。

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

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

只要了解解析器的工作原理,就可以从中粗略地得出一个关键的观察结果。 (记住,Javascript 仍然在被解析(read: compiled)和 then 运行)所以从直接意义上来说,这些括号是 “陈述显而易见”.

说了那么多,究竟是怎么回事?

基本上,圆括号(函数参数除外)被折叠成包含符号的适当分组。 IANAL 但是,通俗地说,这意味着括号仅被解释为指导解析器如何对读取的内容进行分组。如果括号的上下文已经“有序”,因此不需要对发出的 AST 进行任何调整,那么发出(机器)代码就好像这些括号根本不存在一样。

解析器或多或少是懒惰的,假设括号是无礼的。 (在这种边缘情况下,这是不正确的)

好的,这到底发生在哪里?

根据规范中的 12.2.1.5 Static Semantics: IsValidSimpleAssignmentTarget

PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)

  1. Let expr be CoveredParenthesizedExpression of CoverParenthesizedExpressionAndArrowParameterList.
  2. Return IsValidSimpleAssignmentTarget of expr.

即如果期望 primaryExpression return 括号内的任何内容并使用它。

因此,在这种情况下,它不会将 (foo) 转换为 CoveredParenthesizedExpression{ inner: "foo" },而是将其简单地转换为 foo,这保留了它是 [=20] 的事实=],因此在句法上,虽然不一定在词法上,但有效。

TL;DR

It's wat.

想要更深入的了解吗?

查看

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

= 操作的左侧部分是 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:

( Expression[In, ?Yield] )

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

所以它至少在语法上是有效的。它是否实际上是有效的 JS 语法取决于另一个因素:早期错误静态语义。这些基本上是散文或算法规则,在某些情况下会使某些生产扩展无效(语法错误)。例如,这允许作者重用数组和对象初始化语法进行解构,但只应用某些规则。在 early errors for assignment expressions 中我们找到

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

我们也可以在 evaluation of assignment expressions 中看到这种区别,其中简单的赋值目标被评估为可以分配给的引用,而不是获取对象和数组文字等解构模式的东西。

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(就像你的例子)和属性是什么。