The order of operations is clearer when you exploit the comma operator inside bracket notation to see which parts are executed when:
var a = {}
var b = {}
try{
// Uncaught TypeError: Cannot set property 'y' of undefined
a
[console.log('x'), 'x']
[console.log('y'), 'y']
= (console.log('right hand side'), b.e = 1);
} catch(err) {
console.error(err);
}
console.log(b.e) // 1
var a = {}
var b = {}
try {
// Uncaught TypeError: Cannot read property 'y' of undefined
a
[console.log('x'), 'x']
[console.log('y'), 'y']
[console.log('z'), 'z']
= (console.log('right hand side'), b.e = 1);
} catch(err) {
console.error(err);
}
console.log(b.e) // undefined
Looking at the spec:
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... (irrelevant)
Call PutValue(lref, rval)
.
PutValue
is what throws the TypeError
:
Let O be ToObject(base)
.
If the result of calling the [[CanPut]]
internal method of O with argument P is false, then
a. If Throw is true, then throw a TypeError exception.
Nothing can be assigned to a property of undefined
- the [[CanPut]]
internal method of undefined
will always return false
.
In other words: the interpreter parses the left-hand side, then parses the right-hand side, then throws an error if the property on the left-hand side can't be assigned to.
When you do
a.x.y = b.e = 1
The left hand side is successfully parsed up until PutValue
is called; the fact that the .x
property evaluates to undefined
is not considered until after the right-hand side is parsed. The interpreter sees it as "Assign some value to the property "y" of undefined", and assigning to a property of undefined
only throws inside PutValue
.
In contrast:
a.x.y.z = b.e = 1
The interpreter never gets to the point where it tries to assign to the z
property, because it first must resolve a.x.y
to a value. If a.x.y
resolved to a value (even to undefined
), it would be OK - an error would be thrown inside PutValue
like above. But accessing a.x.y
throws an error, because property y
cannot be accessed on undefined
.