深入理解”连等赋值“问题

131 阅读2分钟

文章来源于深入理解”连等赋值“问题

    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.xb.x的那个时间点ab这两个名称指向同一个对象