JavaScript 连续赋值问题
今天被同事问了一个 js 的基础问题 —— 连续赋值,例如:
/* example0 */
var a = 0,
b = 1,
c = 2; // 声明变量并分别赋值
a = b = c = 3; // 连续赋值
console.log(a, b, c); // 3 3 3
按照通常的理解(以往的经验),连续赋值之后 a b c 都等于 3 ,通常情况是没问题,但是,下面这个例子就颠覆了以往的理解:
/* example1 */
var a = { n: 0 },
b = a; // 声明变量并分别赋值
a.x = a = { n: 2 }; // 连续赋值
console.log(a, b); // { n: 2 } { n: 0, x: { n: 2 } }
按照以往的理解根据执行顺序的不同拆分连续赋值,当时考虑了以下几种情况来解释这个结果:
/* case0 */
a = { n: 2 };
a.x = { n: 2 };
// a { n: 2, x: { n: 2 } }
// b { n: 0 }
/* case1 */
a = { n: 2 };
a.x = a;
// a { n: 2, x: [Circular] } 循环引用
// b { n: 0 }
/* case2 */
a.x = a;
a = { n: 2 };
// a { n: 2}
// b { n: 0, x: { n: 0 } }
/* case3 */
a.x = { n: 2 };
a = { n: 2 };
// a { n: 2 }
// b { n: 0, x: { n: 2 } }
从结果上来看,只有 case3 是对的,可是,如果在 example1 和 case3 后面分别加上一句 console.log(a === b.x)
,会发现 example1 的结果为 true
,case3 的结果为 false
,所以 case3 也不正确。
当然,还能列举出其他情况,穷尽猜想来证实某个结果
。不过,既然已经关系到最基本的 =
操作符的处理逻辑,为何不查询一下标准文档的解释呢?
ecma-262
关于 =
操作符的执行顺序描述如下:
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 the following conditions are all true:
- Type(lref) is Reference is true
- IsStrictReference(lref) is true
- Type(GetBase(lref)) is Environment Record
- GetReferencedName(lref) is either “eval” or “arguments”
- Call PutValue(lref, rval).
- Return rval.
试着翻译:
语句 LeftHandSideExpression = AssignmentExpression 将会按照如下顺序进行计算:
- 用 lref 代表 LeftHandSideExpression 的计算结果
- 用 rref 代表 AssignmentExpression 的计算结果
- 用 rval 代表 rref 的值
- 检查如果如下条件全部满足时,则抛出语法错误:
- lref 为引用类型
- lref 为严格引用类型
- lref 为引用类型类型时,lref 的基础值的类型为 Environment Record
- lref 为引用类型类型时的引用名为 “eval” 或者 “arguments”
- 将 lref 代表的表达式的左边赋值为 rval
- 返回 rval
按照标准里的执行顺序来解释 example1 中的连续赋值:
- 定义 lref 为 a.x (a 指向的实际对象的属性 x)
-
计算 a = { n: 2 }
(1). 定义 lref1 为 a
(2). 定义 rref1 为 { n: 2 } 的引用
(3). 定义 rval1 为 rref1 的值
(4). 检查通过
(5). a 赋值为 rval1
(6). 返回 rval1
- 定义 rref 为 rval1
- 定义 rval 为 rref 的值
- 检查通过
- lref (a 指向的实际对象的属性 x) 赋值为 rval
- 返回 rval
b
始终指向原来的实际对象,所以 b
为 { n: 0, x: { n: 2 } }
,a
为 { n: 2 }
。
关于 b.x === a
的结果,按照现在的理解来看应该是 false
,实际结果是 true
。显然,不论是理论上还是实际上,结果都应该是 true
,不然,按照这个理解,总不能让 var a = {}; var b = a; console.log(a === b);
打印出 false
吧。所以应该是对如下的内容理解有误,导致有的步骤理解错误。
- GetValue
- Type
- IsStrictReference
- Environment Record
- GetBase
- GetReferencedName
- PutValue
关于这些内容的说明已经完完全全“超纲”!确实也没有能完全理解。希望有高人指点,或者再继续专研,寻求更深层次的理解!
自此,停笔,休息,待续~~~