为什么这是一个无效的任务左侧?
为什么我可以做以下操作:
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
也不是LeftHandSideExpression
的ArrayLiteral
和IsValidSimpleAssignmentTarget
,它是一个早期的参考错误。
IsValidSimpleAssignmentTarget
在哪里:
12.14.3节 – 赋值运算符 – 静态语义:IsValidSimpleAssignmentTarget
AssignmentExpression : YieldExpression ArrowFunction LeftHandSideExpression = AssignmentExpression LeftHandSideExpression AssignmentOperator AssignmentExpression
1.返回false。
现在回头看看你的代码: b1 = !b2 = true
。 b1 = !b2
很好,因为它是LeftHandSideExpression = AssignmentExpression
,因此对于IsValidSimpleAssignmentTarget
返回true。 当我们检查!b2 = true
时出现问题。 如果我们看一下LeftHandSideExpression
的定义:
第12.3节 – 左手边的expression
句法
LeftHandSideExpression : NewExpression CallExpression
(您可以在上面的规范链接中查看CallExpression
和CallExpression
的定义)
你可以看到!b2 = true
不是一个有效的AssignmentExpression
,因为它不符合标准LeftHandSideExpression = AssignmentExpression
。 这是因为!b2
不是一个有效的LeftHandSideExpression
,也不是一个ObjectLiteral
或ArrayLiteral
,因此IsValidSimpleAssignmentTarget
返回false,抛出ReferenceError
。 请注意,该错误是一个早期的错误 ,这意味着它在任何代码执行之前被抛出,正如@ Bergi的评论所述 。
您可以通过执行以下任一操作来解决此问题,具体取决于您所期望的结果:
b1 = !(b2 = true);
括号内的括号内优先于外部。 这样, b2
被分配,并且因为它是true
,所以括号内部的计算结果为true
。 接下来,它相当于:
b1 = !(true);
正如上面提到的那样,括号内的内容被评估为true
。 b1
将如预期的那样与b2
相反,并且b2
将是true
。
如果你想b1
是true
, b2
是false
,重构这样的陈述:
b2 = !(b1 = true);
这样,与上面的完全相反,给出b1 = true
, b2 = 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)
括号所示的=
赋值运算符优先 。
顺序是:
-
b2
是undefined
因为它尚未初始化。 - 那么
!b2 === true
as!undefined === true
,所以!b2
成为true
, - 然后分配发生,所以
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");