20 September 2013

神奇的JS连续赋值语句

无意中看到这么一个很诡异的问题:

var a = {n:1};
a.x = a = {n:2};
console.log(a);//Object {n: 2}
console.log(a.x);//undefined

按照我的了解,JS中,赋值语句返回的是表达式右边的值,而连续赋值语句的运算顺序是从右自左,那么这个表达式应该是这样运行的:

a.x = (a = {n:2});

其中 a = {n:2} 返回 {n:2} ,然后进行 a.x = {n:2};

但是事实上不是这样,真是百思不得其解啊!研究调试了半天,发现这里有一个引用的指向问题。

那就是JS中,对象和数组属于引用类型,在将它们赋值给别人时,传的是内存地址,当修改任一个时,修改的是内存中的值,所有指向这个地址的变量都将改变。

而如果当将其中一个赋以另一个值后,这个变量指向了新地址,关联打破。

比如说:

var a = {n:1};
var b = a; //a,b指向同一个内存地址
b.n = 2;
console.log(a);//Object {n: 2} //改变一个,所有指向这个地址的变量都改变

b = {n: 1};//赋另外一个值
console.log(a);//Object {n: 2}
console.log(b);//Object {n: 1}  现在指向不同地址   

好,了解这个以后,我们看回原来的问题。

现在我们在将 {n:1} 赋给 a 后,再将 a 赋值给 b ,如下:

var a = {n: 1};
var b = a;

OK,现在 ab 指向同一个地址,我们在将上面那个连续赋值执行一遍:

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x);//undefined
console.log(a);//Object {n: 2}
console.log(b);//Object {n: 1, x: Object}
console.log(b.x);//Object {n: 2}

看到结果,可能有些童鞋已经猜到这里面发生什么了。

我们先假设:在 b=a 后,a , b 指向同一个地址 O1

当执行完 a = {n:2} 后,实际上 a 对象已经指向另一个内存地址了,我们假设这个地址为 O2 。可能是由于JS是先解析后执行还是什么的问题(这里是我个人猜想,对JS底层了解的同学麻烦帮忙解释下…)

既JS在解析时,认定 a.x 中的 a 是指向原先的地址 O1 ,所以在执行赋值 a.x = {n: 2} 时,是将 {n: 2} 赋给了 O1 中的变量。

而最后我们访问 a.x 时,是访问的 O2 中的 a.x ,而我们根本没有给 O2 指向的变量赋值,当然是 undefined

b 变量,由于一直指向 O1 ,所以查看 b ,可以看到 {n: 1, x: Object}

有点绕,总结下。

那就是由于JS是先解析,后执行,所有变量的声明,指向都是在解析时完成的(个人理解),而非执行时进行的。

当例子中 a.x = a = {n:2} 时,由于连续赋值语句是从右自左,先将 a = {n: 2} 执行,执行后 a 在内存中的地址已经改变了,而下一句 a.x = {n: 2} 时,由于解析时决定 a.x 中的 a 指向的是原地址,所以 a.x = {n: 2} 干的是给原来 a 的内存地址指向的变量赋了值。

最后查询 console.log(a.x) ,由于 a 现在是指向新地址,而我们只给 a 的旧地址的 a.x 赋了值,新地址中的 a.x == 'undefined'

Posted in 2013-09-20 15:28