【问题标题】:JavaScript evaluation order when assigning分配时的 JavaScript 评估顺序
【发布时间】:2023-03-29 16:18:01
【问题描述】:
JavaScript 在什么时候确定赋值的左侧——是在评估右侧之前还是之后?
例如,这段代码是做什么的?
var arr = [{thing:1},{thing:2},{thing:3},{last:true}];
arr[arr.length - 1].newField = arr.pop();
【问题讨论】:
标签:
javascript
assignment-operator
operator-precedence
【解决方案1】:
首先计算赋值运算符的左侧。
ES2015 的规范可以在"Runtime Semantics: Evaluation" portion of the "Assignment Operators" section 中找到,可以非常粗略地总结为:
- 评估 LHS 以确定参考
- 评估 RHS 以确定值
- 将值分配给引用
关于数组.pop() 示例,它可能看起来像是会将最初的最后一项分配给结果最后一项中的一个字段——但这只有在首先评估 RHS 而不是 LHS 时才会发生.
实际发生的是左侧首先产生对原始最后一个对象{last:true} 的引用。在此之后,array.pop() 在将其从数组末尾删除后返回相同的对象。然后分配发生,因此对象最终看起来像obj =
{last:true, newField:obj}。由于原始示例中没有保留对obj 的引用,
将赋值扩展到代码,并添加几个额外的变量以便我们检查行为,可能看起来像这样:
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.")
【解决方案2】:
监控此分配期间发生的事情的一个好方法是在数组上定义一个代理:
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();
现在控制台将显示何时访问数组属性(包括数字索引、length 和pop 方法)以及设置它们的时间。
前两行说明首先计算左侧:
获取长度
得到 3
其他行在pop的执行过程中产生:
越来越流行
长度
获得 3
将长度设置为 3