为什么这是一个无效的赋值左侧?

37

为什么我可以执行以下操作:

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开发人员指南表示:

某个地方出现了意外的赋值。例如,可能是由于一个赋值运算符和一个比较运算符不匹配。单个“=”符号将一个值分配给变量,而“==”或“===”运算符则比较一个值。

所有的赋值运算符都可以单独工作,这已经被证明过了,那么为什么不能将它们组合成一个单一的操作/链式赋值呢?


13
实际上,你试图执行的是:var b1, b2; !b2 = true; b1 = b2; 这显然是错误的。你不能将表达式赋值给一个值... - Andrew Li
b2不是一个检查真假的函数,你应该把它放在括号里,就像你在进行条件链接一样。如果引用具有相等的数据,则可以为真。 - HuntsMan
4个回答

71
当你尝试这样做时:
var b1, b2;

b1 = !b2 = true;

document.write(b1, " ", b2);

因为它们在功能上是等价的,所以你基本上在执行以下操作:
var b1, b2;

!b2 = true;
b1 = true; //just the value of b2, not b2 itself

document.write(b1, " ", b2);

!b2 = true 这行中,你试图将一个求值表达式(左侧)赋给一个值,这完全没有意义。可以这样理解:
  • !b2被赋值为true!b2是一个表达式,被计算为一个布尔值,而不是变量。
  • 这就好比做1 + 1 = 2。由于1 + 1被计算为一个值,你不能将其赋给另一个值2。你必须将一个值赋给一个变量,因为值之间的赋值在语义上和逻辑上都是无效的。
  • 另一种思考上述问题的方法是认识到: 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也不是ArrayLiteralLeftHandSideExpressionIsValidSimpleAssignmentTarget为false,则会出现早期引用错误。

IsValidSimpleAssignmentTarget的定义如下:

Section 12.14.3 - Assignment Operators - Static Semantics: IsValidSimpleAssignmentTarget

AssignmentExpression :
  YieldExpression
  ArrowFunction
  LeftHandSideExpression = AssignmentExpression
  LeftHandSideExpression AssignmentOperator AssignmentExpression

1. Return false.

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

Section 12.3 - Left-Hand-Side Expressions

Syntax

LeftHandSideExpression :
  NewExpression
  CallExpression

您可以在上面的规范链接中查看NewExpressionCallExpression的定义。

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


您可以通过以下任一方式来解决这个问题,具体取决于您想要的结果:
b1 = !(b2 = true);

带有括号时,括号内的优先级高于外部。这样,b2 被赋值,并且因为它是 true,所以括号内被计算为 true。接下来,它等同于:

b1 = !(true);

作为之前提到的,括号内的内容被评估为true。因此,b1将是与预期相反的b2的值,而b2将是true
如果您希望b1trueb2false,请按以下方式重构语句:
b2 = !(b1 = true);

这样做与上述情况完全相反,会使 b1 = true,而b2 = false

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

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


2
@DavidTheWin 你确定吗?b1 = !b2 = true 看起来好像想让 !b2 为 false,这意味着 b2 应该是 true,而 b1 应该是 false。 - Andrew Li
2
我理解为“b1和!b2都是真的”,因此b2是假的。 - DavidTheWin
2
实际上是 b1 = true; 而不是 b1 = b2;。赋值操作的结果只是右操作数,而不是对左操作数进行求值。对于简单的词法变量来说这没有关系,但对于更复杂的赋值目标可能会有影响。 - Bergi
2
@Bergi 是的,我提到它们在这种情况下是功能等效的。我会相应地进行编辑,谢谢! - Andrew Li
2
@DavidTheWin 最初的意图是让b1为假,b2为真。现在这个例子是正确的。 - Zze
显示剩余6条评论

27
b1 = b2 = true;

等同于

b2 = true;
b1 = true;

一个赋值语句返回右操作数。在交互式控制台(如Chrome DevToolsNodeJSjsc)中可以很容易地看到。请参见Andrew的答案中的规范详细信息

node interactive console assignement return value example

当你尝试 b1 = !b2 = true;,等价的语句没有意义:

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

这是因为在=赋值运算符上,!优先级较高,如(!b2)中所示。

执行顺序如下:

  1. b2未初始化,因此为undefined
  2. 然后!b2 === true,因为!undefined === true,所以!b2变成了true
  3. 然后执行赋值,所以true = true

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

b1 = !(b2 = true);

1
谢谢您的回答,特别是提到优先级的参考。我不明白:!b2 = true; // true = true; 为什么在我还没有分配 b2 的情况下就是 true = true - Zze
2
@Zze 因为一开始 b2 === undefined。然后 !b2 === true,因为 !undefined === true,所以 !b2 变成了 true,然后赋值操作发生,所以 true = true - Emile Bergeron
2
实际上应该是 b1 = true; 而不是 b1 = b2;。赋值操作的结果仅是右操作数,而不是对左手边进行求值。对于简单的词法变量来说并没有关系,但对于更复杂的赋值目标可能有影响。 - Bergi
@Bergi 参考这个例子:var b1, b2; b1 = b2 = [],根据你的评论,似乎 b1b2 应该指向不同的数组,但实际上它们都指向同一个数组,因此我认为 b1 = b2 是正确的。难道我漏掉了什么吗? - dashtinejad
3
不,我从未说过右操作数被重新计算。我的观点只是b2永远不会用于提供值,它仅仅是赋值的目标。实际发生的情况更像是{ const temp []; b2 = temp; b1 = temp; } - Bergi

10

表达式b1 = !b2 = true;从右往左计算。

首先:!b2 = true

然后:b1 = <之前赋值的结果>

!b2 = true在逻辑上没有意义,因此会出错。

如果您写成b1 = b2 = true;,就不会出现这样的错误。


该表达式根本不被评估,因为它在语法上是无效的,并且在解析时会立即抛出错误。 - Bergi

3

提一下AST将rvalue赋值 (1) (2)

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

var b1, b2;
b1 = !b2 = true;

确认表达式从未被评估。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接