Why can I do the following operations:
var b1, b2;
b1 = b2 = true;
console.log(b1, " ", b2);
b1 = !b2;
console.log(b1, " ", b2);
b1 = b2 = !true;
console.log(b1, " ", b2);
Yet when I try the following operation I receive a “ReferenceError: invalid assignment left-hand side”?
var b1, b2;
b1 = !b2 = true;
console.log(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 an assignment operator and an equality 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?
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 to true
. !b2
is an expression and is evaluated to a boolean value, not variable. 1 + 1 = 2
. Since 1 + 1
is evaluated to a value, you can't assign that to 2
, another value. You must assign a value to variable, as value-to-value assignment is semantically and logically invalid. 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 as 2
has value 2
, it cannot be changed. What if we tried 1 - 1 = 2
? 0
, a constant and value, cannot be 2
, 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.