JavaScript中包含两个赋值运算符的复合表达式的求值过程

The evaluation process of a compound expression containing two assignment operators in JavaScript

这是一个包含两个赋值运算符的复合表达式:

var a = {n: 1};
var b = a;
a.x = a = {m: 2};
a;    // => {m: 2}
b;    // => {n: 1, x: {m: 2}}

棘手的部分是第三行:

a.x = a = {m: 2};

恕我直言,赋值运算符=是右结合的,所以表达式的嵌套结构是:

a.x = (a = {m: 2});

但是ES5中的求值顺序总是从左到右,根据ES5 Annex D

根据ES5 Section 11.13.1,

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let rref be the result of evaluating AssignmentExpression.
  3. Let rval be GetValue(rref).
  4. Throw a SyntaxError exception if the following conditions are all true: ....omitted intentionally to save space
  5. PutValue(lref, rval).
  6. Return rval.

所以我对从左到右求值顺序的理解是:

  1. 先评估 a.x 并 return 参考 lref1
  2. a = {m: 2}得到rref1,因为也是赋值表达式,我们再开始程序(像递归)

    2.1。首先评估 a 并 return 对其进行参考 lref2

    2.2。评估 {m: 2} 和 return 对象 {m: 2}rref2

    2.3。设rval2 = GetValue(rref2),所以rval2也是对象{m: 2}

    2.4。 PutValue(lref2, rval2),因此 a 将重新绑定对象 {m: 2} 而不是 {n: 1}

    2.5。 returnrval2,即对象{m: 2}rref1(不是引用类型,而是对象)

  3. rval1=GetValue(rref1),也正是对象{m: 2}

  4. PutValue(lref1, rval1),所以lref1指向的内存地址就是{m: 2}。并且 b.x 仍然引用此地址,并且 b 将被更新。

这个过程符合 ES5 规范并且很好地解释了结果。

我的问题是:

  1. 上面这个评价顺序是真是假?如果为假,还有其他解释吗?

  2. 如何正确理解ES5中的Reference Specification Type?它只是一个指向特定内存地址的中间指针吗?

是的,您对运算符顺序的理解似乎是正确的。

ECMAScript 5 section 8.7 说:

A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).

从 属性 访问创建引用的过程定义在 11.2.1:

  1. Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

因此引用 lref1 包含 a 对象值 (连同引用的名称字符串 "x"),您最初使用 {n: 1} 创建。引用不关心它来自的变量;它只关心创建时提供的基本值参考名称

改变 a 持有的值对引用 lref1 持有的基值没有任何影响。 lref1 继续保留 a 的原始值(即 {n: 1} 对象),而不管 a 在创建 lref1 后做了什么。

简而言之,从表达式 a.x 创建的引用在创建引用后就不再与变量 a 有任何关系。相反,引用只知道 a 在创建引用时持有的值。