文章来源于深入理解”连等赋值“问题
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); // --> undefined
alert(b.x); // --> {n: 2}
想要理解这个连等赋值的问题,关键是要理解以下两个问题:
- JS引擎对赋值表达式的处理过程
- 赋值运算的右结合性
赋值表达式
a = b这样的表达式就叫做赋值表达式,其中a和b又分别可以是表达式,但是a必须是一个左值
左值的定义就是一个可以被赋值的表达式,foo,foo.bar都可以是一个表达式,函数名fn也可以是一个表达式,但是fn()一个函数调用的表达式就不可以了
js引擎是这样计算a = b的:
- 计算表达式a 得到一个a 的引用 refA
- 计算表达式b 得到一个值 valueB
- 将valueB赋值给refA指向的名称的绑定
- 返回valueB
结合性
结合性就是,指表达式中同一个运算符出现多次时,是左边的优先计算还是右边的优先计算。
而js 中赋值表达式是右结合的,那么
a1 = a2 = a3 =a4
就等价于
a1 = (a2 = (a3 =a4))
连等的解析
对于单个赋值运算,js引擎总是先计算左边的操作数,再计算右边的操作数,所以上面的式子的解析就是
- 计算a1得到 ref1
- 计算a2得到 ref2
- 计算a3得到 ref3
- 计算a4得到 value4
先从左到右解析各个引用,然后计算最右侧的表达式的值,最后把值从右到左赋给各个引用。 这几个步骤提现了上面所说的右结合性
解决问题
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); // --> undefined
alert(b.x); // --> {n: 2}
首先前两行执行完成之后,a和b同时指向对象{n: 1},这应该是毫无疑问的,然后执行第三行a.x = a = {n: 2};
- 根据上面的解析先计算
a.x得到ref1,这个时候的a指向的是对象{n: 1},所以ref1是这个对象的x属性的引用 - 然后计算
a得到ref2 - 然后计算
{n: 2}得到值value - 将
value值赋值给ref2,a的引用本来指向的是{n: 1}这个对象,在这里就被修改了,所以到这一步 a = {n: 2} - 将
value值赋值给ref1,因为在前面ref1指向了{n: 1},所以表达式中的a.x就是{n: 1}[x],所以最后的{n: 1}就变为了{ n: 1, x:{n: 2} }- 一开始
b指向的就是{n: 1},所以b.x就是{n: 2} - 而最后
a被赋值了{n: 2},所以a.x就是undefined - 同时
b.x === a
- 一开始
在最后
a.x = a = {n: 2} 等价于b.x = a = {n: 2};因为在解析a.x或b.x的那个时间点。a和b这两个名称指向同一个对象