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

Zze*_*Zze 37 javascript language-lawyer

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

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);
Run Code Online (Sandbox Code Playgroud)

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

var b1, b2;

b1 = !b2 = true;

document.write(b1," ", b2);
Run Code Online (Sandbox Code Playgroud)

很明显,我不能这样做,但我无法找到解释为什么我不能.错误的MDN开发人员指南指出:

某处有意外的任务.例如,这可能是由于赋值运算符和比较运算符的不匹配造成的.虽然单个"="符号为变量赋值,但"=="或"==="运算符会比较一个值.

所有赋值运算符都经过验证,因此为什么不能将它组合成单个运算/链式赋值?

Li3*_*357 71

当你尝试这样做:

var b1, b2;

b1 = !b2 = true;

document.write(b1, " ", b2);
Run Code Online (Sandbox Code Playgroud)

因为它们功能相同你基本上是这样做的:

var b1, b2;

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

document.write(b1, " ", b2);
Run Code Online (Sandbox Code Playgroud)

在该行中!b2 = true,您试图将一个表达式(一个值(左侧))赋值给一个值 - 这完全没有意义.以这种方式思考:

  • !b2正被分配到true.!b2是一个表达式,并被计算为布尔,而不是变量.
  • 这与做的类似1 + 1 = 2.由于1 + 1评估为某个,因此无法将其分配给2另一个值.您必须为变量赋值,因为值到值赋值在语义上和逻辑上都是无效的.
  • 考虑上述问题的另一种方法是实现这一点:1 + 1是一种价值.2是一个价值.您无法为值指定值,因为该值已具有值.一个常数,如有22,它不能改变.如果我们尝试1 - 1 = 2怎么办?0,常数和价值,不能2,因为它是一个常数.

因此,为值赋值是在语义上和逻辑上无效的.您不能分配02就像你不能分配falsetrue.

如果您想更好地理解语法和语义,以及为什么会这样ReferenceError,您可以深入研究ECMAScript®2015语言规范.根据规格:

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

AssignmentExpression : LeftHandSideExpression = AssignmentExpression

  • 这是一个早期参考错误如果LeftHandSideExpression既不是ObjectLiteral也不是ArrayLiteralIsValidSimpleAssignmentTargetLeftHandSideExpression是假的.

在哪里IsValidSimpleAssignmentTarget:

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

AssignmentExpression :
  YieldExpression
  ArrowFunction
  LeftHandSideExpression = AssignmentExpression
  LeftHandSideExpression AssignmentOperator AssignmentExpression
Run Code Online (Sandbox Code Playgroud)

1.返回假.

现在回顾一下你的代码:b1 = !b2 = true.b1 = !b2很好,因为它是LeftHandSideExpression = AssignmentExpression,因此返回真实IsValidSimpleAssignmentTarget.我们检查时会出现问题!b2 = true.如果我们看一下定义LeftHandSideExpression:

第12.3节 -左侧表达式

句法

LeftHandSideExpression :
  NewExpression
  CallExpression
Run Code Online (Sandbox Code Playgroud)

(可以查看的定义NewExpressionCallExpression以上说明书中的链接)

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


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

b1 = !(b2 = true);
Run Code Online (Sandbox Code Playgroud)

使用括号时,括号内优先于外部.这样,b2分配,并且因为它true,在括号内评估true.接下来,它相当于:

b1 = !(true);
Run Code Online (Sandbox Code Playgroud)

如上所述,在括号内部进行评估true.b1将与b2预期相反,b2并将成为true.

如果你想b1成为trueb2将来false,重组这样的声明:

b2 = !(b1 = true);
Run Code Online (Sandbox Code Playgroud)

这样,它与上面的完全相反,给出b1 = true,和b2 = false.


正如@Bergi在评论中提到的那样,b1分配了正确的操作数,true在这种情况下,不是!b2.

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

  • @DavidTheWin 你确定吗?`b1 = !b2 = true` 似乎他们希望 `!b2` 为 false,这意味着 `b2` 应该为 true,而 `b1` 应该为 false。 (2认同)
  • 我把它读作"b1和!b2都是真的",因为b2因此是假的 (2认同)
  • 实际上它是`b1 = true;`不是`b1 = b2;`.赋值操作的结果只是右操作数,而不是评估左侧.对于简单的词法变量并不重要,但可以用于更复杂的分配目标. (2认同)
  • @Bergi是的,我提到它们在这种情况下功能相同.我会相应编辑,谢谢! (2认同)
  • @DavidTheWin最初的意图是使b1为假,而b2为真.这个例子目前是正确的. (2认同)

Emi*_*ron 27

b1 = b2 = true;
Run Code Online (Sandbox Code Playgroud)

相当于

b2 = true;
b1 = true;
Run Code Online (Sandbox Code Playgroud)

一个赋值返回正确的操作.这很容易交互式控制台(如内看到Chrome的DevTools,的NodeJS,JSC).请参阅Andrew的答案中规格细节.

节点交互式控制台assignement返回值示例

当你尝试时b1 = !b2 = true;,等价物毫无意义:

(!b2) = true; // true = true; causes the error.
b1 = b2;      // never evaluated.
Run Code Online (Sandbox Code Playgroud)

这是因为!需要优先=赋值运算符这表现在括号中(!b2).

订单如下:

  1. b2undefined因为它尚未初始化.
  2. 然后!b2 === true作为!undefined === true,所以!b2变得true,
  3. 然后分配发生,所以true = true.

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

b1 = !(b2 = true);
Run Code Online (Sandbox Code Playgroud)

  • @Zze因为`b2 === undefined`一开始.然后`!b2 === true`为`!undefined === true`,所以`!b2`变成'true`,然后分配发生,所以`true = true`. (2认同)
  • 实际上它是`b1 = true;`不是`b1 = b2;`.赋值操作的结果只是右操作数,而不是评估左侧.对于简单的词法变量并不重要,但可以用于更复杂的分配目标. (2认同)
  • @dashtinejad不,我从未说过重新评估了正确的操作数.我的观点是'b2`从不用于提供值,它只是一个赋值的目标.实际上发生的事情更像是`{const temp []; b2 = temp; b1 = temp; }`. (2认同)

mri*_*rid 10

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

第一: !b2 = true

然后 : b1 = <result of previous assignment>

!b2 = true 没有逻辑意义,因此错误.

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