JavaScript Code Trick: What's the Value of Foo.X

JavaScript code trick: What's the value of foo.x

foo.x = foo = {n: 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.

See section 11.13.1 of the ES5 spec:

  1. Let lref be the result of evaluating LeftHandSideExpression.

  2. Let rref be the result of evaluating AssignmentExpression.

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.

What is x && foo()?

Both AND and OR operators can shortcut.

So && only tries the second expression if the first is true (truth-like, more specifically). The fact that the second operation does stuff (whatever the contents of foo() does) doesn't matter because it's not executed unless that first expression evaluates to something truthy. If it is truthy, it then will be executed in order to try the second test.

Conversely, if the first expression in an || statement is true, the second doesn't get touched. This is done because the whole statement can already be evaluated, the statement will result in true regardless of the outcome of the second expression, so it will be ignored and remain unexecuted.

The cases to watch out for when using shortcuts like this, of course, are the cases with operators where defined variables still evaluate to falsy values (e.g. 0), and truthy ones (e.g. 'zero').

Double-assignment of an object property results in undefined property

foo.x = foo = { n: 2 };

The foo.x refers to the property x of the object referred to by foo. However, foo = { n: 2 } assigns a completely new object to foo. x is indeed assigned to an object, but that object is immediately replaced by another object. The object with the x property isn’t referenced by anything anymore.

You can read that line as

foo.x = (foo = { n: 2 });


Graphical explanation

var foo = { n: 2 };

First object is assigned to foo

foo.x = foo = { n: 2 };

foo is reassigned to a second object, but foo.x still points to the first object.

console.log(foo.x);

Trying to access foo.x actually accesses the x property of the second object.

Assignment associativity

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

Assignment associativity

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

Why does this code create the object, but still treats it as undefined?

I was wrong. This should throw an exception even before evaluating the rhs. It's a bug (or at least, deviation from the spec) in the engine. See https://es.discourse.group/t/rhs-evaluation-in-assignment-to-unresolvable-reference/310.


That's just how it works:

  1. The left-hand side is evaluated to a reference
  2. The right-hand side is evaluated to a value
  3. The value is put in the reference, which throws an error if the reference cannot resolve its base value

I don't know why they didn't bother checking the reference before evaluating the right-hand side, however this behaviour is consistent with a setter property throwing.



Related Topics



Leave a reply



Submit