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
关于这些内容的说明已经完完全全“超纲”!确实也没有能完全理解。希望有高人指点,或者再继续专研,寻求更深层次的理解!
自此,停笔,休息,待续~~~