Assignment associativity [duplicate] - javascript

This question already has answers here:
JavaScript code trick: What's the value of foo.x
(6 answers)
Closed 6 years ago.
The assignment operator has right-to-left associativity. So
var x,y;
x=y=1;
works as expected and x equals 1. Consider now the code:
var foo={};
foo.x = foo = {n: 2};
I would expect the above to work like the following:
var foo = {n: 2};
foo.x=foo;
However, in the first case foo.x is undefined while in the second case foo.x points to foo (circular reference). Any explanation?

JavaScript evaluates expressions from left to right. We can show what's going on by using an additional variable:
var foo = {};
var bar = foo; // keep a reference to the object originally stored in foo
foo.x = foo = {n: 2};
Because of associativity, the last statement is parsed as:
foo.x = (foo = {n: 2});
But because of evaluation order, foo.x runs first (determining where to store the value), then foo, then {n: 2}. So we store {n: 2} in the variable foo, then assign the same value to the property x of the old contents of foo ... which we can see by looking at bar:
foo = {"n" : 2}
bar = {"x" : {"n" : 2 }}

Related

What is output for foo.x

var foo = {n: 1};
var bar = foo;
foo.x = foo = {n: 2};
what is output for foo.x.
My thought is evaluation from right to left
so it is equal to
foo = {n:2};
foo.x = foo
so it will be
{
n: 1
x: {n: 2}
}
But it is not, it is undefined? I am get confused, looking for an explanation.
This is the expected behavior. See the specification:
Simple Assignment ( = )
The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
Let lref be the result of evaluating LeftHandSideExpression.
Let rref be the result of evaluating AssignmentExpression.
Let rval be GetValue(rref).
Throw a SyntaxError exception if (...unimportant)
Call PutValue(lref, rval).
Return rval.
In short, when the interpreter sees x = <expression>, or x.prop = <expression>, it first identifies what to assign to - that is, the LeftHandSideExpression. Then, after evaluating the right-hand side (the AssignmentExpression) to a value, it assigns the value to what was initially identified as the LeftHandSideExpression. So, with
foo.x = foo = {n: 2};
foo.x mutates the original foo object, not the reassigned foo = {n: 2} object, so after the foo.x = foo = {n: 2} line, foo refers to the new {n: 2}, which is never mutated.
You can see how bar, which references the same object in memory as the original foo, gets mutated:
var foo = {n: 1};
var bar = foo;
foo.x = foo = {n: 2};
console.log(bar);

Variable initialisation in JavaScript

var foo = {n: 1};
foo.x = foo = {a: 2};
console.log(foo.x);
// Output -- undefined
var a = 1;
c = b = a;
console.log(a,b,c);
// Output -- 1, 1, 1
Could you please explain why first example returns undefined where as second returns 1,1,1?
That happens because expressions in javascript are evaluated left to right.
So in this expression:
foo.x=foo={a:2}
first the foo is dereferenced and a property x is set to be the target of the right operand foo={a:2} result.
Then, while the right operand is evaluated, the foo value is reassigned, so a reference to the previous instance of the object is lost.
To demonstrate it we may simply create another variable to keep it:
var foo = {n:1};
var bar = foo;
foo.x=foo={a:2};
console.log(foo, bar);
That's because the {a:2} object is actually being assigned to the {n:1}.x property! Not to {a:2}. There is no "x" property in it.
What we have here is:
var foo = {n:1};
foo.x = foo = {a:2}; // equals:...
{n:1}.x = {a:2};
/* note that foo.x is actually referring to {n:1} object! */
foo = {a:2}; // ==>
/* while foo is being pointed to a different object
which is now the {a:2} object!
wherefore ...*/
{a:2}.x // is normally undefined i.e.: never set!
However:
{n:1}.x // has been successfully assigned with:
>> {a:2} object.
But because you are loosing a reference to the object {n:1}, and have no other way to examine it. In order to verify and prove that it is {n:1} who is actually receiving the property "x", we'll have to give it a backup reference before we overwrite the foo variable in order to be able to return it.
var foo = {n:1};
var bak = foo;
foo.x=foo={a:2};
console.log(foo.x);
console.log(bak.x);
>> undefined
>> {a:2}.
foo.x = foo = {a: 2};
determines that foo.x refers to a property x of the {n: 1} object, assigns {n: 2} to foo, and assigns the new value of foo – {n: 2} – to the property x of the {n: 1} object.
The important thing is that the foo that foo.x refers to is determined before foo changes.
The assignment operator associates right to left, so you get:
foo.x = (foo = {n: 2})
The left hand side is evaluated before the right hand side and hence an undefined value

How does a.x = a = {n: b} work in JavaScript?

This is related to Javascript a=b=c statements.
I do understand that
foo = foo.x = {n: b}; // console.log(foo) => {n: b}
but
foo.x = foo = {n: b}; // console.log(foo) => {n: b}
It should equal to :
foo = {n: b};
foo.x = foo; // console.log(foo) => {n: b, x:object}
Am I missing something here?
With:
foo.x = foo = {n: b};
The leading foo.x is partially evaluated first, enough to determine the exact target for the assignment, before proceeding to actually assign it.
It behaves more along the lines of:
var oldFoo = foo;
foo = {n: b};
oldFoo.x = foo;
This is mentioned in the standard. The left side of the = is evaluated (1.a) before the value is placed there (1.f):
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
1) If LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral, then
a) Let lref be the result of evaluating LeftHandSideExpression.
...
f) Perform ? PutValue(lref, rval).
This is because when you write
var foo = {};
foo.x = foo = {n: b} //a=b=c
while the line is being executed, foo is pointing to {} but when this statement is broken down to
foo.x = (foo = {n: b}) /a=(b=c)
foo's reference has changed from {} to {n:b} but foo in foo.x (a) is still pointing to old reference of foo since left hand expression was evaluated before assignment had begun.
As per the spec
If LeftHandSideExpression is neither an ObjectLiteral nor an
ArrayLiteral,
a. then Let lref be the result of evaluating
LeftHandSideExpression.
Which means before the assignment foo.x was still having reference to old foo.
So, if you tweak your example a little bit by doing
var foo = {z:2};
foo.x = foo.n = {n: 1};
In this example, you didn't change the reference to foo, only assigned new property, so the output now is
Object {z: 2, n: Object, x: Object}
Now, it has retained the reference to old foo since a new reference was not assigned hence all properties z, n and x are being retained.
It equals
let tmp = foo;
foo = {n: b};
tmp.x = foo;
You could see, that old foo (stored in z in this example) was modified:
> z=foo={};
{}
> foo.x = foo = {n: b};
{ n: 10 }
> foo
{ n: 10 }
> z
{ x: { n: 10 } }
I got it.
var foo = {}; // now foo is a reference point to object {}
foo.x = foo = {n:1}; // first foo is refer to a new object {n:1}, then old foo referred object {} set a prop x
// try this to get what you want
var foo = foo1 = {};
foo.x = foo = {n:1};
console.log(foo, foo1) // here foo1 is what you want

Multiple assignment confusion

I understand that the assignment operator is right associative.
So for example x = y = z = 2 is equivalent to (x = (y = (z = 2)))
That being the case, I tried the following:
foo.x = foo = {a:1}
I expected that the object foo would be created with value {a:1} and then the property x will be created on foo which will just be a reference to the foo object.
(This is actually what happens if I was to separate the multiple assignment statement into two separate statements foo = {a:1};foo.x = foo; )
The outcome was actually:
ReferenceError: foo is not defined(…)
So then I tried the following:
var foo = {};
foo.x = foo = {a:1};
Now I don't get the exception anymore but foo.x is undefined!
Why is the assignment not working as I expected?
Disclaimer: The 'duplicate' question seems to be very different to the one that I'm asking, as the issue there is that the variables that were created in the assignment were global, as apposed to variables created with the var keyword. That's not the issue here.
There's an important difference between associativity and order of evaluation.
In JavaScript, even though the assignment operator groups right to left, the operands are evaluated left to right before the actual assignments are performed (which do occur right to left). Consider this example:
var a = {};
var b = {};
var c = a;
c.x = (function() { c = b; return 1; })();
The variable c initially references a, but the right-hand side of the assignment sets c to b. Which property gets assigned, a.x or b.x? The answer is a.x because the left-hand side is evaluated first, when c still references a.
In general, the expression x = y is evaluated as follows:
Evaluate x and remember the result.
Evaluate y and remember the result.
Assign the result from step 2 to the result of step 1 (and return the former as the result of the expression x = y).
What happens with multiple assignments, as in x = (y = z)? Recurse!
Evaluate x and remember the result.
Evaluate y = z and remember the result. To do this:
Evaluate y and remember the result.
Evaluate z and remember the result.
Assign the result from step 2.2 to the result of step 2.1 (and return the former as the result of the expression y = z).
Assign the result from step 2 to the result of step 1 (and return the former as the result of the expression x = (y = z)).
Now let's look at your example, slightly edited:
var foo = {};
var bar = foo; // save a reference to foo
foo.x = (foo = {a:1}); // add parentheses for clarity
foo.x is evaluated before foo gets assigned to {a:1}, so the x property gets added to the original {} object (which you can verify by examining bar).
Edited the answer to make it simple
First of all you have to understand the differnce between Reference- and Value- Type.
var foo = {};
foo variable holds a Reference to an object in memory, lets say A
Now, there are two arts of accessors: Variable Accessor and Property Accessor.
So foo.x = foo = {a:1} can be understood as
[foo_VARIABLE_ACCESSOR][x_PROPERTY_ACCESSOR] = [foo_VARIABLE_ACCESSOR] = {a:1}
!!! Accessor chain is evaluated first to get the last accessor, which is then evaluated associative.
A['x'] = foo = {a:1}
Property Accessor are seperated into setters and getters
var foo = { bar: {} };
foo.bar.x = foo = {a:1}
Here where have decared two nested objects foo and bar. In memory we have then two object A and B.
[foo_VAR_ACCESSOR][bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> A[bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B[x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B['x'] = foo = {a: 1}
Here you have little example
var A = {};
var B = {}
Object.defineProperty(A, 'bar', {
get () {
console.log('A.bar::getter')
return B;
}
})
Object.defineProperty(B, 'x', {
set () {
console.log('B.x::getter')
}
});
var foo = A;
foo.bar.x = foo = (console.log('test'), 'hello');
// > A.bar.getter
// > test
// > B.x.setter
Great question. The thing to remember here is that JavaScript uses pointers for everything. It's easy to forget this since it is impossible to access the values representing memory addresses in JavaScript (see this SO question). But realizing this is very important in order to understand many things in JavaScript.
So the statement
var foo = {};
creates an object in memory and assigns a pointer to that object to foo. Now when this statement runs:
foo.x = foo = {a: 1};
the property x is actually getting added to the original object in memory, while foo is getting assigned a pointer to a new object, {a: 1}. For example,
var foo, bar = foo = {};
foo.x = foo = {a: 1};
shows that if foo and bar are pointing to the same object initially, bar (which will still point to that original object) will look like {x: {a: 1}}, while foo is simply {a: 1}.
So why doesn't foo look like {a: 1, x: foo}?
While you are right in that assignments are right associative, you must also realize that the interpreter still reads from left to right. Let's take an in-depth example (with some bits abstracted out):
var foo = {};
Okay, create an object in memory location 47328 (or whatever), assign foo to a pointer that points to 47328.
foo.x = ....
Okay, grab the object that foo currently points to at memory location 47328, add a property x to it, and get ready to assign x to the memory location of whatever's coming next.
foo = ....
Okay, grab the pointer foo and get ready to assign it to the memory location of whatever's coming next.
{a: 1};
Okay, create a new object in memory at location 47452. Now go back up the chain: Assign foo to point to memory location 47452. Assign property x of the object at memory location 47328 to also point to what foo now points to--memory location 47452.
In short, there is no shorthand way to do
var foo = {a: 1};
foo.x = foo;

Chained assignment and circular reference in JavaScript

Consider the following statements:
var foo = {n: 1};
foo.bar = foo = {n: 2};
Can you explain why foo.bar is undefined instead of being foo?
When executing the assignment operator, JS evaluates the left part first. So this
foo.bar = foo = {n: 2};
is interpreted as
evaluate foo.bar. This returns a reference {base: Object {n:1}, property:bar}.
then evaluate the second assignment:
2.1 eval foo. This returns a reference {base:<scope>, property:foo}
2.2. eval {n:2}. This creates a new object.
2.3 put value: <scope>.foo = {n:2}
2.4 return {n:2}
put value to the first reference: {n:1}.bar = {n:2}. This runs fine, but the old object {n:1} is not accessible anymore, since <scope>.foo already refers to the new object
Details: http://ecma-international.org/ecma-262/5.1/#sec-11.13.1
If you make a copy of foo before, you'll see that the leftmost = actually modifies the old object:
var foo = {n:1};
var oldFoo = foo;
foo.bar = foo = {n: 2};
document.write(JSON.stringify(foo) + "<br>")
document.write(JSON.stringify(oldFoo) + "<br>")
So by the time the assignment for foo.bar happens, the reference is "filled in" for foo. which makes it the original object.
Let's expand your code a bit to make it more clear.
var foo1, foo2;
foo1 = foo2 = {n:1};
foo1 === foo2; // true
foo1.bar = foo1 = {n:2}
foo1.bar === foo2; // false
foo1 === foo2; // false
There are two objects at play here. One will have a bar property, the other wont. To show this, i'll store the original object in another variable for comparison.
var foo = {n: 1};
var orig = foo;
foo.bar = foo = {n: 2};
console.log(foo, orig); // {n:2}, {n:1, bar: {n:2}}
Until the foo.bar line is done executing, foo still contains the original object, so the bar property of the original object will be set to the new object.
You changed the object to which foo refers. There is no bar in {n: 2}. What did you expect?

Categories