Zze
Zze

Reputation: 18835

Why can’t I use a unary operator in a chained assignment, but only in separate assignment statements?

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?

Upvotes: 37

Views: 7381

Answers (4)

mrid
mrid

Reputation: 5796

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

Upvotes: 10

Andrew Li
Andrew Li

Reputation: 57972

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.
  • This would be analogous to doing 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.
  • 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 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 an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget of LeftHandSideExpression 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.

Upvotes: 71

prosti
prosti

Reputation: 46449

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.

Upvotes: 3

Emile Bergeron
Emile Bergeron

Reputation: 17430

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.

node interactive console assignement return value example

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:

  1. b2 is undefined since it was not initialized yet.
  2. Then !b2 === true as !undefined === true, so !b2 becomes true,
  3. and then the assignment occurs, so true = true.

You can make it work as you expect by adding parentheses:

b1 = !(b2 = true);

Upvotes: 27

Related Questions