At what point does JavaScript determine the left-hand side of an assignment — is it before or after the right-hand side is evaluated?
For example, what does this code do?
var arr = [{thing:1},{thing:2},{thing:3},{last:true}];
arr[arr.length - 1].newField = arr.pop();
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:
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.")
A nice way to monitor what is happening during this assignment, is defining a proxy on the array:
var arr = [{thing:1},{thing:2},{thing:3},{last:true}];
arr = new Proxy(arr, {
get: function (obj, key) {
console.log('getting ', key);
return obj[key];
},
set: function (obj, key, value) {
console.log('setting ', key, ' to ', value);
return obj[key] = value;
}
});
arr[arr.length - 1].newField = arr.pop();
Now the console will show when the array properties (including numerical indexes, length and pop method) are accessed and when they are set.
The first two lines illustrate that the left side is evaluated first:
getting length
getting 3
The other lines are generated during the execution of pop:
getting pop
getting length
getting 3
setting length to 3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With