为什么这是一个无效的任务左侧?

为什么我可以做以下操作:

var b1, b2; b1 = b2 = true; document.write(b1," ", b2); document.write("<br>"); b1 = !b2; document.write(b1," ", b2); document.write("<br>"); b1 = b2 = !true; document.write(b1," ", b2); 

然而当我尝试下面的操作时,我收到一个ReferenceError: invalid assignment left-hand side

 var b1, b2; b1 = !b2 = true; document.write(b1," ", b2); 

很显然,我不能这样做,但我找不到解释,为什么我不能。 有关错误的MDN开发人员指南指出:

有一个意想不到的任务。 例如,这可能是由于赋值运算符和比较运算符不匹配造成的。 当一个“=”符号给一个variables赋值时,“==”或“===”运算符比较一个值。

所有的赋值操作符都是单独工作的,所以为什么不能把它们合并成一个单独的操作/链接赋值呢?

当你尝试这样做:

 var b1, b2; b1 = !b2 = true; document.write(b1, " ", b2); 

因为它们在function上是等同的,所以你基本上在做:

 var b1, b2; !b2 = true; b1 = true; //just the value of b2, not b2 itself document.write(b1, " ", b2); 

在这一行!b2 = true ,你试图把一个expression式赋值为一个值(左边),这是毫无意义的。 这样想一想:

  • !b2正在分配给true!b2是一个expression式,并被评估为一个布尔 ,而不是variables。
  • 这将类似于做1 + 1 = 2 。 由于1 + 1被评估为一个 ,所以不能将其分配给2 ,即另一个值。 您必须为variables赋值 ,因为值到赋值语义上和逻辑上无效。
  • 另一种思考上述的方法是实现这个: 1 + 1是一个值。 2是一个值。 您无法将值分配给值,因为该值已具有值。 一个常数如2具有值2 ,它不能被改变。 如果我们尝试1 - 1 = 2呢? 0 ,一个常数和值,不能是2 ,因为它是一个常数。

因此,将一个值赋值给一个语义上和逻辑上是无效的。 您不能将0赋值为2 ,就像您不能将false赋给true

如果您想更好地理解语法和语义,为什么会引发ReferenceError ,那么您可以深入研究ECMAScript®2015 Language Specification 。 根据规范:

12.14.1节赋值运算符 – 静态语义:早期错误

AssignmentExpression : LeftHandSideExpression = AssignmentExpression

  • 如果LeftHandSideExpression既不是ObjectLiteral也不是LeftHandSideExpressionArrayLiteralIsValidSimpleAssignmentTarget ,它是一个早期的参考错误。

IsValidSimpleAssignmentTarget在哪里:

12.14.3节赋值运算符 – 静态语义:IsValidSimpleAssignmentTarget

 AssignmentExpression : YieldExpression ArrowFunction LeftHandSideExpression = AssignmentExpression LeftHandSideExpression AssignmentOperator AssignmentExpression 

1.返回false。

现在回头看看你的代码: b1 = !b2 = trueb1 = !b2很好,因为它是LeftHandSideExpression = AssignmentExpression ,因此对于IsValidSimpleAssignmentTarget返回true。 当我们检查!b2 = true时出现问题。 如果我们看一下LeftHandSideExpression的定义:

第12.3节左手边的expression

句法

 LeftHandSideExpression : NewExpression CallExpression 

(您可以在上面的规范链接中查看CallExpressionCallExpression的定义)

你可以看到!b2 = true不是一个有效的AssignmentExpression ,因为它不符合标准LeftHandSideExpression = AssignmentExpression 。 这是因为!b2不是一个有效的LeftHandSideExpression ,也不是一个ObjectLiteralArrayLiteral ,因此IsValidSimpleAssignmentTarget返回false,抛出ReferenceError 。 请注意,该错误是一个早期的错误 ,这意味着它在任何代码执行之前被抛出,正如@ Bergi的评论所述 。


您可以通过执行以下任一操作来解决此问题,具体取决于您所期望的结果:

 b1 = !(b2 = true); 

括号内的括号内优先于外部。 这样, b2被分配,并且因为它是true ,所以括号内部的计算结果为true 。 接下来,它相当于:

 b1 = !(true); 

正如上面提到的那样,括号内的内容被评估为trueb1将如预期的那样与b2相反,并且b2将是true

如果你想b1trueb2false ,重构这样的陈述:

 b2 = !(b1 = true); 

这样,与上面的完全相反,给出b1 = trueb2 = false


作为@Bergi在评论中提到, b1被赋予了正确的操作数,在这种情况下是true ,而不是!b2

虽然大多数浏览器目前不支持ECMAScript 6(2015)的所有function,而是使用ECMAScript 5.1(2011) ,但两个版本的规格相同。 所有的定义都是一样的,因此解释依然有效。

 b1 = b2 = true; 

相当于

 b2 = true; b1 = true; 

赋值返回右操作数 。 在交互式控制台(如Chrome DevTools , NodeJS , jsc )中很容易看到。 请参阅Andrew的答案中的规格细节 。

节点交互式控制台分配返回值的例子

而当你尝试b1 = !b2 = true; ,相当于没有意义:

 (!b2) = true; // true = true; causes the error. b1 = b2; // never evaluated. 

这是因为! (!b2)括号所示=赋值运算符优先

顺序是:

  1. b2undefined因为它尚未初始化。
  2. 那么!b2 === true as !undefined === true ,所以!b2成为true
  3. 然后分配发生,所以true = true

您可以通过添加括号使其按预期工作:

 b1 = !(b2 = true); 

expression式b1 = !b2 = true; 从右到左进行评估

首先: !b2 = true

那么: b1 = <result of previous assignment>

!b2 = true不合逻辑,因此错误。

如果你写b1 = b2 = true; ,它不会给出任何这样的错误

只要提到AST 赋值右值 ( 1 )( 2 )

 if (1 + 1 = 2) console.log("1 + 1 = 2");