js中参数传递为什么必定是按值传递,有些疑惑,以下是一些理解。
js高级程序设计(第三版)的第四章,表明了基本数据类型(Undefined、Null、Boolean、Number、String)是按值访问的,对象是按引用访问的。
首先,看下引用类型变量是怎样的(obj1、obj2都是对象类型):
var obj1 = new Object();var obj2 = obj1;
简单来说,obj1、obj2都是变量,他们分别有一个指针,各自指向各自的栈内存,然后栈内存中存放有堆内存地址和一些别的数据,那么就又有第二个指针根据栈内存中存放的地址指向堆内存,堆内存中存放的是同一个的对象(因为obj1和obj2栈内存中的地址是相同的)。
注意到书中原话:
当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。
然后,实验代码如下:
var t1 = new Object();var t2 = t1;t2.name ="t1";t2 = new Object(); //本质为t2的指针指向了一个新空间console.log(t1.name); //输出t1console.log(t2.name); //输出undefined
分析:
var t2 = t1;
这行代码实质是把t1栈内存中的数据复制在t2的栈内存中,栈内存中存放的堆内存地址就是一样的,因而指向了同一个堆内存地址,即指向同一个对象。
t2 = new Object();
这行代码就是把t2在栈内存中存放的地址改变了,变成了一个此刻不在使用的地址,然后t2的指针指向了这个新地址,所以之后t1和t2无关了。
可以看出,书中写的 复制操作结束后,两个变量实际上将引用同一个对象。 指两个变量是两个独立的变量,唯独在于他们各自的对象都是在同一个堆内存空间的。我通俗理解为:t1和t2两个变量本身是按值访问的,t1和t2指向的对象是按引用访问的,但实际上定义变量,自然是为了操作变量指向的对象。
下面是对于函数参数传递中的一些理解:
先看一下,让人误以为参数是引用传递的代码:
var t1 = new Object();function change(obj){ obj.name = "lalala";}change(t1);console.log(t1.name); //输出了lalala
结果是外部的t1多了一个属性name,原因就在于确实是参数传递,把t1按值传递给了obj,操作他们各自属性的时候,属性仍然是按引用传递的,我们传的参数又不是t1的属性。
强调一下,我上面说t1的属性,是不正确的说法,严格来说,不是t1多了一个属性,也不是t1的属性,而是t1指向的对象多了一个属性,或者说那是t1指向的对象的属性,t1只是一个变量罢了。
再看下面代码:
var t1 = new Object();function change(obj){ obj.name = "t1"; obj = new Object(); obj.name = "new t1";}change(t1);console.log(t1.name); //输出t1
输出结果,t1.name仍然是‘‘t1’’,再进行obj = new Object()时候,因为栈内存中数据的改变,这里的obj最终指向的对象不是t1指向的对象了,就函数内部再也操作不到t1一开始指向的对象了,类似于下面代码中t2的重新声明。
var t1 = new Object();var t2 = t1;t2.name ="t1";t2 = new Object();console.log(t1.name); //输出t1console.log(t2.name); //输出undefined
结论:函数的参数传递是按值传递的,但对于参数是引用类型变量,该变量指向的对象的属性(就是t1.name之类的)是按引用访问的,因此改变对象的属性会反映在外部对象上;但变量本身按值传递了,因此改变内部变量(就只改变t1、t2),外部变量不会有改变。就是实际上没有把堆内存中的对象作为函数参数。