The left-hand side of an assignment operator is evaluated first.
The specification for this as of ES2015 can be found in the "Runtime Semantics: Evaluation" portion of the "Assignment Operators" section and can be very roughly summarized as:
- Evaluate the LHS to determine the reference
- Evaluate the RHS to determine the value
- Assign the value to the reference
With regards to the array .pop() example, it might have looked like it would assign the originally last item to a field in the resulting last item — but that would only happen if the RHS were evaluated first, rather than the LHS.
What actually happens is that the left-hand side first yields a reference to the original last object {last:true}. After this, the array.pop() returns the same object after removing it from the end of the array. Then the assignment happens, so that the object ends up looking like obj =
{last:true, newField:obj}. Since no reference to obj was kept in the original example,
Expanding the assignment out to code instead, and adding a couple extra variables so we can check the behavior, might look something like this:
function pseudoAssignment() {
// left-hand side evaluated first
var lhsObj = arr[arr.length - 1];
var lhsKey = 'newField';
// then the right-hand side
var rhsVal = arr.pop();
// then the value from RHS is assigned to what the LHS references
lhsObj[lhsKey] = rhsVal;
// `(a = b)` has a value just like `(a + b)` would
return rhsVal;
}
var arr = [{thing:1},{thing:2},{thing:3},{last:true}];
_lastObj = arr[arr.length - 1];
// `arr[arr.length - 1].newField = arr.pop();`
_result = pseudoAssignment();
console.assert(_lastObj.newField === _lastObj,
"The last object now contains a field recursively referencing itself.")
console.assert(_result === _lastObj,
"The assignment's result was the object that got popped.")
console.assert(arr.indexOf(_lastObj) === -1,
"The popped object is no longer in the array.")