js变量在内存的存放解惑

js变量的类型

我们知道js变量有两种类型

  • 基本类型值,指简单的数据段,对于undefined、null、boolean、number、string这5种简单数据类型可以直接操作保存在变量中的实际值,也就是按值访问。
  • 引用类型值,指那些可能由多个值构成的对象,只能操作对象的引用而不是实际的对象,所以要得到引用类型这种值只能按引用访问。

变量的存放方式

有如下代码段(赋值基本类型)

1
2
var a=1;
var b=1;

上述代码定义了两个变量,定义a=1在栈内存开辟一块空间存放1这个值,同理定义b=1也开辟一块内存存放1这个值;
当修改变量的值的时候,比如重新赋值a=3,那么就会重新开辟一块内存,将3存入后,把a指向3所在的内存位置;

有个问题还没考证,就是不同的变量定义的值一样的时候,是不是指向的是同一块内存。在python中可以通过id来访问存放位置的id,在值(基本类型)较小的时候,就是指向同一块内存

有如下代码段(赋值引用类型)

1
2
var c={name:'阿Q'};
var d={name:'阿Q'};

上述代码定义了两个变量,定义c的时候在栈内存开辟一块空间存放一个内存地址,这个内存地址指向堆内存中放{name:'阿Q'}这个对象的内存;d同样如此,而且两个对象是毫无关系的,他们的内存地址是不一样的。
当修改变量的值的时候,如c={name:'老王'},这种情况就和基本类型一样,属于重新赋值,就会将对象{name:'老王'}存到堆内存中,将对象的地址存到栈内存中,c指向这个内存;
当修改变量,如c.name='老张',这种情况可以看作给对象做一个修正或者扩展,不属于重新赋值,虽然结果是修改了对象,但并没有改动对象所在的位置,即c指向的栈内存里存放的对象的地址并没有变;
当然如果如下定义

1
2
3
var c=d={name:'阿Q'};
// 或者
var a=b=1;

这样的连等赋值,有另外的说法,之后专写一篇关于连等赋值的问题。

函数传参是按值传递还是引用传递

对于这块内容,网上很多博文说法很多,还有说共享传递啥的,在此我们不对这些字面进行讨论,我们就解释下本质到底是怎么个情况。
比如有如下代码

1
2
3
4
5
6
var a = 1;
function foo(x) {
x = 2;
}
foo(a);
console.log(a); // 仍为1, 未受x = 2赋值所影响

这个就无需多解释了,x作为一个形参,只在函数内部生效,而且当a当作参数传给x的时候,其实就是复制了一份a指向的栈内存中存放的值,所以a和x就是毫无关系了
如果a为一个引用类型,如下代码

1
2
3
4
5
6
var obj = {x : 1};
function foo(o) {
o.x = 3;
}
foo(obj);
console.log(obj.x); // 3, 被修改了!

此处也一样,x作为一个形参,当obj当作参数传给o的时候,其实是复制了一份obj指向的栈内存中存放的对象的地址,所以obj和o在栈内存中是不一样的,但在栈内存中存的东西是一样的,就是对象{x : 1}所在的地址;
所以当修改o的时候,自然obj也就被修改了
还有人提出以下代码

1
2
3
4
5
6
var foo = {name:'foo'};
function test(o){
o = {name:'bar'};
}
test(foo);
console.log(foo.name); // foo,未被修改;

这个结果是没问题的,但这就是上文提到的对对象的修改的方式的问题,是直接赋值去修改变量,还是去修改对象的属性;
上述代码foo当作参数传给o,同样将对象所在地址赋值一份给了形参o,但函数中o的赋值操作,使形参o完全指向了另外一个对象的内存地址,这个操作,并不影响实参foo在内存中存放的地址,及地址所指向的对象;

参考资料

JS中函数的参数是按值传递还是按引用传递
JavaScript参数按值传递的理解
JS进阶系列之内存空间