Chained assignment and circular reference in JavaScript - 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?

Related

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

Assignment associativity [duplicate]

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 }}

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;

variables passed by reference or by value (clarification)

This is my example code:
var foo = {a : 1};
var bar = foo;
console.log(bar.a);//1, as expected
foo.a = 2;
console.log(bar.a);//2, as expected, as objects are passed by reference
foo = {a : 10};
console.log(bar.a);//2, not expected, I expected 10
The last log doesn't give the expected result.
Thinking that foo = {a : value} is the same as foo.a = value I expected that last result was 10.
What's wrong with my expectation? I think I am missing a big lesson here.
You set foo initially to one object:
var foo = {a : 1};
and later overwrite it with completely new object:
foo = {a : 10};
In this case foo and bar are no longer connected, because the reference is now broken.
When you reach this line of code:
foo = {a : 10};
foo is assigned a new variable; from this point on foo and bar are two different variables and for this reason the last line prints 2 as bar is still pointing to the old value.
You are reassigning foo to a new object literal on the 2nd to last line.
Bar still points to the old object, thus you get 2 (which you modified on the 4th line).
If that explanation doesn't clear things up, it might be helpful to step back and try to understand variables, objects, and references on a higher level. Eloquent JavaScript's Data Structures chapter might be a good place to start.
edit: A point worth clarifying: it's important to understand you're not overriding anything, you just changed foo's reference. They point at different things in memory now. That's why bar is the same.
var foo = {a : 1}; //foo refering to object {a : 1}
var bar = foo; //bar refering to same object as foo
console.log(bar.a); //1, as expected
foo.a = 2; //The object to which bar and foo are pointing gets changed
console.log(bar.a); //2, as expected, as objects are passed by reference
foo = {a : 10}; //foo starts pointing to the newly created object, whereas bar is still refering the old object(no change in bar) and the earlier object `{a : 1}` exists and poinyted to by bar
console.log(bar.a); //2, not expected, I expected 10 //hence this result
first you have assigned foo to bar at that time the ref of foo is sent to bar.
now if you change the value bar.a then foo will be changed
foo = {
a: 2
}
bar = foo
console.log(bar.a) //gives 2
bar.a = 30
console.log(foo.a) //gives 30
but when you over-write foo, the connection will be lost and both act like 2 individual variables
so now
foo.a = 20
console.log(bar.a) //gives 30
console.log(foo.a) //gives 20

Categories