JavaScript – Chained Assignment of Variables with Operators

javascriptoperatorsvariable-assignment

I want to do something to a couple of variables using operators in quick succession. I don't think what I want to do is important as such; my question is more about the fundamentals of JavaScript evaluation.

In the three examples below, I try to use addition to change the values of two variables. However, not all perform as I (perhaps naïvely) expected.

JSFiddle here.

  1. OPERATIONS AS THREE SEPARATE STATEMENTS

    var a = 9, b = 2;
    a += b; b += a; a += b;
    // a === 24, b === 13
    
  2. OPERATIONS SEPARATED BY COMMA OPERATOR

    var a = 9, b = 2;
    a += b, b += a, a += b;
    // AS EXPECTED: a === 24, b === 13
    
  3. OPERATIONS IN ONE STATEMENT/EXPRESSION

    var a = 9, b = 2;
    a += (b += (a += b)); 
    // BUT HERE WE GET THIS: a === 22, b === 13
    

In the last example, b evaluates as expected, but a evaluates to a number two short of what appears in the first two examples.

I think that this is because everything in the parentheses returns the correct value but is finally added to the original value of a, i.e. 9, rather than the value suggested by (a += b) earlier in precedence which would be 11.

I have looked for why this might be in Flanagan's JavaScript: The Definitive Guide (6th ed.), (particularly under 4.11.1 "Assignment with operation") but found nothing there. Crockford doesn't seem to mention it explicitly in The Good Parts either. I've used various search terms to try to find more information on this behaviour. Can anyone tell me what this phenomenon is called or point me to some information on this behaviour (assuming it is expected) or what I might be doing wrong (assuming it's not)?


NB. I'm conscious that the parentheses in example 3 may be redundant since, as I understand it, assignment precedence goes from right-to-left anyway. But I thought having them there would make the example easier to talk about.


UPDATE

Judging by the answers below, I think my confusion over this issue actually stems from having absorbed a couple of paragraphs from the Flanagan book, possibly incorrectly:

In most cases, the expression:

a op= b

where op is an operator, is equivalent to the expression:

a = a op b

In the first line, the expression a is evaluated once. In the second, it is evaluated twice. The two cases differ only if side a includes side effects such as a function call or an increment operator. The following two assignments, for example, are not the same:

data[i++] *= 2
data[i++] = data[i++] * 2

I took this to mean that my one line example should produce the same results as the other two, because:

  1. Flanagan mentions two evaluations occurring in a = a op b as opposed to one, implying this is in fact different to a op= b where a is not evaluated as an lval on the right.
  2. I assumed the assignment operator I used (e.g. a += b) would count as a side-effect.

IMHO, I think Flanagan has made this confusing and it seems to contradict what's in the ECMAScript convention (as pasted below by pocka), but it could be my reading/misinterpretation. Is what he is saying incorrect or simply unclear? Or, is it just me?

Best Answer

I think (not sure, although this is counter-intuitive) you can imagine:

a += (b += (a += b));

being written as:

a = a + (b += (a += b));

Although the plus + operator has right to left associativity, JavaScript expressions are evaluated from left-to-right, so a is evaluated first which is 9 now, then (b += (a += b)) is evaluated to 13.

Now the + operator adds from right-to-left, thus adding 13 to 9 and giving us 22.

EDIT: I am not gonna comment directly on your questions because I feel confused by reading them :).

Instead I am gonna try to explain this differently. I think the main source of your confusion comes from the difference between operator precedence, associativity and order of evaluation.

I really advise you to read the part on the Order of Evaluation (4.7.7 in the book, which is a great book by the way).

Let's go with an example first:

var x =1, y = 2, z = 3;
var alpha = (z=4) + y * z;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 4
console.log(alpha);  // 12

In this example, although the multiplication operator * has higher precedence than the summation operator +, the the evaluation of the different components of the entire expression is still from left-to-right.

alpha on the left is declared and created first, then (z=4) is evaluated, then y is evaluated to 2. Now z is evaluated again which results in 4, notice that this is the new value which is caused by the side-effect of assigning 4 to z earlier in the expression, remember (z=4).

This results in an overall value for alpha that is equal to 12.

Now back to our original expression:

a += (b += (a += b));

a on the left is evaluated first which is 9 now, then the first b to the left is evaluated which is now 2, now the second a is evaluated which is 9 also, then the last b to the right is evaluated which is again 2.

Now starts the real work, because of the parentheses the last (a += b) is evaluated so now we have a = 11, then (b += (a += b)) is evaluated which is now 13, now this value is summed the value already evaluated which is 9 resulting in 22.

If it hasn't happened this way, this would mean that a on the left side of = would have been evaluated twice which is not the case.

Summary: You can't update the value of an already evaluated expression.

I hope this can clear this for you, if you have any further questions then feel free to ask :)

Related Question