js按值及按引用传递详解
值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实际参数的值。
引用传递:(形式参数类型是引用数据类型参数):也称为传地址。方法调用时,实际参数是对象(或数组),这时实际参数与形式参数指向同一个地址,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,这个结果在方法结束后被保留了下来,所以方法执行中形式参数的改变将会影响实际参数。
纠错:第一段尼古拉所谓的对象按值传递的说法并无错处,理由是地址值本身就是一个原始值,它是可以如同值传递一样被覆盖掉的。也即当一个引用类型的实参传进来的时候,它是将这个引用类型的指针所占用的一块内存复制给了形参,形参具有了一块跟实参一模一样的内存,但二者不是一个。当然它俩所指向的堆内存中的对象是一样的。这就是尼古拉所谓的值传递的核心含义。
第二段,引用传递的定义说的就是地址传递。后面一句“person就会自动被修改为指向其name属性值为"Greg"的新对象”,怎么会修改呢?前面插入了新的赋值,一个在堆内存中完全的崭新的区块被创建出来了,自然地址值就发生了改变,obj这个以前的形参变量就被覆盖掉啦!这一步涉及到js中函数内部变量的优先级问题。与引用不引用没什么关系。函数内部修改了参数的值,原始的引用仍然不变,这个说法有问题,obj.name = "Nicholas",这本身就是在函数内部对堆内存对象的修改,怎么说原始的引用不变呢,明明变了好不好?后面的说法比较正确。不过局部对象一说有些问题,在js中只有局部变量一说,何来局部对象?当然你说变量是对象类型啊,所以称他为局部对象也解释的过去。不过终究这样说容易造成误导。当然要访问这个对象,的确只能通过该函数入口。
第三、四段:前面已阐述清楚,不赘述。
第五段,“这个例子中person对象不是当做参数传递的,但person.name的值还是"Nicholas",这和person当做参数传递的情况是一样的。可以确定的是:这个例子中对象是按引用传递的。但按照尼古拉的说法这个例子中对象也是按值传递的。 那么可以得出结论:尼古拉的说法是错的。我们可用图来说明一下这个问题。”说白了跟前面所描述的没什么区别,只不过将像形参变量传递地址值改成了向一个普通变量传地址值而已。其他并无差别。
几幅图的解读正确。但上述描述有些混乱,如果纯粹一个初学者,基本无法看明白,而且还有误导。
第六段的解释很混淆,不能这么理解,堆内存中的那么对象你称之为Object,这很容易跟构造函数Object混淆,另外这个Object也容易造成person,obj跟Object混淆。你可以这么说,那个堆内存中的匿名对象,它在堆内存中的地址值分别放在了person跟obj当中。你任意操纵其中一个,实际本质上都是在操纵堆内存中的那个匿名对象。后面的解读跟我上面说的差不多,不赘述。
在这里我们要理清一个问题。
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); // "Nicholas"
按常规来说,如果obj后面的赋值没有用var的话,它会泄露到全局环境中去,而实际情况是你无法在全局中访问到obj。为什么呢?原因是在函数内部obj在预编译阶段并非没有声明,他作为一个形参变量已经被声明了,也就是说在栈内存中有那么一块叫obj的内存块存在。此时赋值就不会再声明一次,直接覆盖那个变量里的值就好了。当然你说相当于var重新声明了一个变量也可以,相当于在预编译阶段obj先作为形参声明,然后作为普通变量声明,但是因为优先级的问题,它的声明被忽略掉了,意思就是还是形参声明的那一次,var或者不var一点影响没有,还多出来一个关键字,必要性不大。
好了,关于本文解析非常清晰了。
1,.按值传递,分为两类,基本类型值的传递和函数参数的传递,而函数参数的传递又有两种,基本类型值的参数传递和引用类型值的参数传递。其中,引用类型值的传递实际上传递的是对象的引用(引用=内存中的地址=值),因为执行的操作是复制保存操作(这一点可以看js高级教程68页的下面的注释),而不是传递的对象本身。
2.按引用传递则很好理解,传递的是完整的对象本身。
这个对于按值及按引用传递的解释才叫真正清楚了。按值传递实际是基本类型值跟引用类型值(就是地址值)两种。
引用类型值的传递和引用传递,一字之差,含义却相差千里。引用类型值传递传递的是一个地址值,而引用传递传递的是对象本身。也就相当于堆内存中一块分配了空间的对象,它有两个名字,你对其中一个赋值,就是对另一个赋值,自然两个都被修改,且原先的对象被覆盖了。这跟引用值传递完全不一样,引用值传递覆盖的只是地址值而已,堆内存的原对象内存块一点不受影响。