Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expressions with conditional and assignment operator

This Javascript expression is working just fine in all browsers (jsfiddle):

false ? 1 : x = 2;

It's evaluating to 2.

But why? I'd expect an exception here, because the left hand side of the assignment is false ? 1 : x, which is not a valid reference. Compare with (jsfiddle):

(false ? 1 : x) = 2;

This one is throwing a ReferenceError. I double checked the Javascript operator precedence table, it states that the conditional operator ? : has higher precedence than the assignment operator =, so both expressions should be identical, at least I though so.

In Java, which has pretty similar syntax and operator precedence rules like Javascript, both expressions above result in a compile time error, which makes perfectly sense.

Can someone explain this difference?

like image 340
GOTO 0 Avatar asked Jun 01 '13 11:06

GOTO 0


2 Answers

As you found at MDN, ? : has a higher precendence than the assignment operator =, which means that JS is reading your statement as:

false ? 1 : (x = 2);

At first glance that might seem backwards, but what it means is that ? : is expecting three operands, with the part on the right of the : being the third operand. Since = has lower precedence x = 2 becomes the third operand.

The alert shows 2 because the assignment x = 2 sets the x variable to 2 and then this (sub)expression evaluates to 2.

Your second version:

(false ? 1 : x) = 2;

...gives a reference error because it does the (false ? 1 : x) part first which evaluates to the value associated with x (undefined), it doesn't return the variable x itself. undefined = 2 doesn't work.

like image 58
nnnnnn Avatar answered Nov 15 '22 15:11

nnnnnn


Here are the two keys to understanding the difference between the JavaScript conditional expression and the Java conditional expression:

Please read the Note at the bottom of this section of the ECMAScript 5 annotated specification:

http://es5.github.io/#x11.12

Now, please read the Java specification section for the conditional expression:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.25

You will note that as the ECMAScript 5 note states, the third operand in the ternary operator in Java cannot be just any old expression - it can only be a ConditionalExpression. However, for ECMAScript 5, the third operand can be any AssignmentExpression.

Looking further at the Java spec, we see that Expression is any assignment expression:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.27

But ConditionalExpression is either the ConditionalExpression with the ternary operator (... ? ... : ...) or just a ConditionalOrExpression (termed LogicalOrExpression in ES5) (see either of the first two links above for that info). The "chain" of what the ConditionalOrExpression can be starts here in Java:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.24

And here in ECMAScript 5:

http://es5.github.io/#x11.11

Following the "chain" of expression types backward in the ECMAScript 5 spec (because it is easier to follow than the Java spec) from ConditionalExpression all the way through basically every other expression but Assignment Expression finally lands us at the beginning - Primary Expression:

http://es5.github.io/#x11.1

The second operand in both of your code snippets above is a primary expression:

1

The upshot of all this rigamarole (if I am correct) is that in Java, the third operand of the ternary operator cannot be an assignment, but in JavaScript it can. That is why both of your examples fail in Java but only the second in JavaScript.

Why does the first one work in JavaScript but not the second?

operand1 ? operand2 : operand3;

works like the following IIFE code (not actually, but the below code is illustrative of how the above works):

(function () { if (operand0) return operand1; else return operand2;}());

So:

false ? 1 : x = 2;

becomes (again, not actually - the below code is illustrative of the above code):

(function () { if (false) return 1; else return x = 2;}());

However, in your second snippet, when using the parens, you separate out explicitly the conditional expression from the ' = 2;':

(false ? 1 : x) = 2;

becomes (again, not actually - the below code is illustrative of the above code):

(function () { if (false) return 1; else return x;}()) = 2;

The "acts like the example IIFE function invocation" behavior of the ternary operator will return whatever x is and that will be a value, not a reference, which cannot be assigned to. Hence the error. This would be like the following code (if x === 3):

3 = 2;

Obviously, one cannot do this.

In Java, I believe, the first one gives an error because the third operator cannot be an assignment, the second one gives an error because you cannot assign to a value (just like in JavaScript).

As far as operator precedence, please look at the following code:

var x = 3;

console.log(false ? 1 : x);          // ?: evaluates to "3"
console.log(false ? 1 : x = 2);      // ?: evaluates to "2"
console.log(false ? 1 : x = 2, 4);   // ?: evaluates to "2" - "2" and "4" arguments passed to log
console.log((false ? 1 : x = 2, 4)); // ?: evaluates to "4"

The first two are easily understood when viewed in terms of the IIFE illustrative code above.

In the first line x is evaluated and the conditional expression evaluates to 3 - that's easy.

In the second line, the best way I can describe it is that the conditional operator (?:) causes even the lower precedence '=' operator to be evaluated as a complete expression not because (?:) has higher precedence, but because as the spec states the assignment expression following the ':' is evaluated (including the ' = 2' part) as an AssignmentExpression. This behavior looks clearer in the return statement in the IIFE examples above. With JavaScript at least, you can have an assignment not only in the second operand but also the third of the conditional expression.

However, in the third line, a complete assignment expression is already found in the "x = 2" expression and the ternary operator uses it as the complete third operand, and the ',' operator being lower in precedence than any other, we get the equivalent to the following code:

console.log((false ? 1 : x = 2), 4);

In the fourth line of code, encapsulating the entire expression within the console.log() statement in parens brings the ', 4' into the '?:' ternary expression as part of the third operand.

The following jsfiddles demonstrate the above discussion with live code. Note that the first two have the same exact error after printing '2' twice:

FIDDLE1

FIDDLE2

FIDDLE3

like image 40
Xitalogy Avatar answered Nov 15 '22 16:11

Xitalogy