在JavaScript中,访问变量有按值(访问基本类型)和按引用(访问引用类型)两种方式,但函数的参数值传递只能按值传递。
首先记住一句话:把函数外部的值传递给函数的形参,就和把值从一个变量复制到另一个变量一样。
一、引用类型的变量复制
众所周知在js中声明了引用类型的变量时,变量名其实只是一个指向堆内存的引用,保存的是数据在堆内存中的地址,引用类型数据的存储形式如下图所示:
当把值从一个变量复制到另一个变量时(注意这里说的是值的复制,而不是深拷贝),其实是让新的变量也指向内存中的同一个位置,如下图所示:
二、值传递
1.基本类型的值作为参数传递时很容易理解,不必赘述。
2.参数传递引用类型
所谓值传递的意思是:把内存中的对象(也就是真正的值)传递给形参,那么如何实现呢?其实只需让形参也指向这个内存中的对象,实现方法就是把实参中的内存地址复制给形参。这个过程与上面的两张图所描述的过程是一样的,其中obj相当于实参,而obj2相当于形参。也正对应了开头的那句话:把函数外部的值传递给函数的形参,就和把值从一个变量复制到另一个变量一样。
但从图中可以看出,如果给obj2.a重新赋值,那么通过obj.a访问的值也会变化,也就是说在函数内部修改了形参值,函数外部的实参也会变化,来看下面的代码:
function setName(obj) {
obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // Nicholas
js的值传递确实有这样的“问题”——当传递的参数是引用类型时,函数内部的修改会被反映到函数外部。这样的结果确实容易让人误认为函数参数是引用传递的,再来看下面的代码:
function setName(obj) {
obj.name = "Nicholas";
obj = new Object(); // 修改了形参的地址引用
obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // 还是Nicholas
函数内部的形参被重新赋值时,也就是形参指向了一个新的地址,此后对形参的修改不会再影响实参。如果参数是引用传递的,那么实参依然会随着形参的变化而变化,这就说明函数参数的传递不是引用传递。
三、总结
- 值传递听起来像深拷贝,但不能理解为深拷贝,也就是说不能理解为:在内存中又创建了一个一模一样的对象,然后让形参指向这个对象;
- 传递的是引用类型时,传递过来的只是实参的一个地址引用,即形参获得的也是一个地址引用,此时形参与实参指向的堆内存是相同的。所以值传递的值可理解为“内存地址”,传递可理解为“复制”。