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,现在 a
, b
指向同一个地址,我们在将上面那个连续赋值执行一遍:
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