纠结的连等赋值
偶的看到一段有意思的代码:
var a = {n: 1}; a.x = a = {n: 2}; console.log(a.x);
作为一个热衷于“钻牛角尖”的人,楼主对这样的代码很感兴趣,也不禁陷入了思考。so也不要说写这样的代码难维护啥的,纯粹为了思考逻辑。
首先,这是一个连等赋值,而且赋值的是个对象。如果是基本的JavaScript类型,不存在引用,也就不会有类似的问题,但是逻辑是一样的。
赋值过程是怎么样的呢?以上的翻译是,b先赋值5,然后(b=5)会返回所赋的值(也就是5),然后再赋值给a,也就是:
a = (b = 5);
所以最初的代码,可以修改成:
var a = {n: 1}; a.x = (a = {n: 2}); // modified here console.log(a.x);
如果是这样,那么以下代码有什么区别:
var a = {n: 1}; // a.x = (a = {n: 2}); // modified here a = {n: 2}; a.x = a; console.log(a.x);
很明显,结果不一样,继续了解下JavaScript的运算顺序。
var a = 10; function fn() { a = 20; return 20; } var b = a + fn(); console.log(b);
输出30,我们猜测JavaScript是从左到右运算的(事实也确实如此)。先取出a在内存中的值(10),然后执行fn函数,返回20(同时改变了a的值),10+20=30。
如果运算过程中有括号啥的,是不是会“智能”地改变运算顺序呢?
var a = 10; function fn() { a = 20; return 20; } var b = a + (fn() * 2); console.log(b);
输出50,还是从左到右,没有智能地先运算括号里的内容,然后改变a的值,再和a相加。这在JavaScript内核中是怎么实现的?这个我也没有研究过,但是我知道C++等语言是没有这么“智能”的,可以通过堆栈来模拟,比如简单计算器。
再回头看这道题,声明一个变量a,指向一个对象{n: 1},然后执行连等。可以肯定的是,a.x和a指向的是同一块内存,我们从左到右运算,a.x实际操作的是a指向的对象,发现没有x这个key,那么就会自动添加,它的值等待右边的运算结果。然后a重新引用了一个对象,并且之前的{n:1}这个对象新的key(x)的value也引用到同一个对象上。大概过程如下图,过程2我把它理解为等待,等待过程3中a的运算结果。
如果要说的稍微“专业”一点,连等是先确定所有变量的指针,再让指针指向那个赋值。
事实上,解析器在接受到 a.x = a = {n:2}
这样的语句后,会这样做:
- 找到 a 和 a.x 的指针。如果已有指针,那么不改变它。如果没有指针,即那个变量还没被申明,那么就创建它,指向 null。
a 是有指针的,指向{n:1}
;a.x 是没有指针的,所以创建它,指向 null。 - 然后把上面找到的指针,都指向最右侧赋的那个值,即
{n:2}
。
如果理解了,试试这道题吧:
var a, b, c, d; a = b = c = d = {a: 1}; a.x = a = b.y = b = c.z = c = {} console.log(a, b, c, d);
参考: