问题
Why can I do the following operations:
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);
Yet when I try the following operation I recieve a ReferenceError: invalid assignment left-hand side
?
var b1, b2;
b1 = !b2 = true;
document.write(b1," ", b2);
It's obvious that I can't do this, but I can't find an explanation as to why I can't. The MDN developer guide for the error states:
There was an unexpected assignment somewhere. This might be due to a mismatch of a assignment operator and a comparison operator, for example. While a single "=" sign assigns a value to a variable, the "==" or "===" operators compare a value.
All of the assignment operators work individually as proven, so why can't this be combined into a singular operation / chained assignment?
回答1:
When you try to do this:
var b1, b2;
b1 = !b2 = true;
document.write(b1, " ", b2);
Because they are functionally equivalent‡ you are basically doing:
var b1, b2;
!b2 = true;
b1 = true; //just the value of b2, not b2 itself
document.write(b1, " ", b2);
In the line !b2 = true
, you are trying to assign an expression that evaluates to a value (the left side) to a value - that makes absolutely no sense. Think about it this way:
!b2
is being assigned totrue
.!b2
is an expression and is evaluated to a boolean value, not variable.- This would be analogous to doing
1 + 1 = 2
. Since1 + 1
is evaluated to a value, you can't assign that to2
, another value. You must assign a value to variable, as value-to-value assignment is semantically and logically invalid. - Another way to think about the above is to realize this:
1 + 1
is a value.2
is a value. You cannot assign a value to a value, as that value already has a value. A constant such as2
has value2
, it cannot be changed. What if we tried1 - 1 = 2
?0
, a constant and value, cannot be2
, because it is a constant.
Thus, it is semantically and logically invalid to assign a value to a value. You cannot assign 0
to 2
just as you can't assign false
to true
.
If you want to understand the syntax and semantics better, and why this throws a ReferenceError
, you can delve into the ECMAScript® 2015 Language Specification†. Per the specification:
Section 12.14.1 - Assignment Operators - Static Semantics: Early Errors
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
- It is an early Reference Error if
LeftHandSideExpression
is neither anObjectLiteral
nor anArrayLiteral
andIsValidSimpleAssignmentTarget
ofLeftHandSideExpression
is false.
Where IsValidSimpleAssignmentTarget
is:
Section 12.14.3 - Assignment Operators - Static Semantics: IsValidSimpleAssignmentTarget
AssignmentExpression : YieldExpression ArrowFunction LeftHandSideExpression = AssignmentExpression LeftHandSideExpression AssignmentOperator AssignmentExpression
1. Return false.
Now look back at your code: b1 = !b2 = true
. b1 = !b2
is fine because it is LeftHandSideExpression = AssignmentExpression
, thus returning true for IsValidSimpleAssignmentTarget
. The problem arises when we check !b2 = true
. If we look at the definition of LeftHandSideExpression
:
Section 12.3 - Left-Hand-Side Expressions
Syntax
LeftHandSideExpression : NewExpression CallExpression
(You can view the definitions of NewExpression
and CallExpression
in the specification link above)
You can see that !b2 = true
is not a valid AssignmentExpression
, as it does not fit the criteria LeftHandSideExpression = AssignmentExpression
. This is because !b2
is not a valid LeftHandSideExpression
, also not an ObjectLiteral
nor ArrayLiteral
, thus IsValidSimpleAssignmentTarget
returns false, throwing the ReferenceError
. Note that the error is an early error, meaning it is thrown before any code is executed, as noted in @Bergi's comment.
You can combat this by doing either of the following, depending on your desired outcome:
b1 = !(b2 = true);
With parentheses, inside the parentheses takes precedence over outside. That way, b2
is assigned, and since it is true
, inside the parentheses evaluates to true
. Next, it's equivalent to:
b1 = !(true);
As inside the parentheses is evaluated to true
as mentioned above. b1
will be the opposite of b2
as expected, and b2
will be true
.
If you wanted b1
to be true
and b2
to be false
, restructure the statement like this:
b2 = !(b1 = true);
This way, it's the exact opposite of the above, giving b1 = true
, and b2 = false
.
‡As @Bergi mentioned in the comments, b1
is assigned the right operand, true
in this case, not !b2
.
†Although most browsers currently do not support all features of ECMAScript 6 (2015), and instead use ECMAScript 5.1 (2011), the specification is the same for both versions. All definitions are the same, and thus the explanation is still valid.
回答2:
b1 = b2 = true;
is equivalent to
b2 = true;
b1 = true;
An assignment returns the right operand. It's easy to see within an interactive console (like Chrome DevTools, NodeJS, jsc). See the spec details in Andrew's answer.
And when you try b1 = !b2 = true;
, the equivalent makes no sense:
(!b2) = true; // true = true; causes the error.
b1 = b2; // never evaluated.
This is because the !
takes precedence on the =
assignment operator as demonstrated by the parentheses in (!b2)
.
The order goes:
b2
isundefined
since it was not initialized yet.- Then
!b2 === true
as!undefined === true
, so!b2
becomestrue
, - and then the assignment occurs, so
true = true
.
You can make it work as you expect by adding parentheses:
b1 = !(b2 = true);
回答3:
Th expression b1 = !b2 = true;
gets evaluated from right to left
First : !b2 = true
then : b1 = <result of previous assignment>
!b2 = true
doesn't make logical sense and hence the error.
If you write b1 = b2 = true;
, it won't give any such error
回答4:
Just to mention the AST Assigning to rvalue (1) (2)
if (1 + 1 = 2)
console.log("1 + 1 = 2");
var b1, b2;
b1 = !b2 = true;
to confirm the expression is never evaluated.
来源:https://stackoverflow.com/questions/40581507/why-is-this-an-invalid-assignment-left-hand-side