今天看到《JavaScript高级程序设计》里面关于参数传递的章节时,有点懵。本着“打破砂锅问到底”的精神,看了些别人写的博客和知乎上一些大神的解释,算是对参数传递有了个比较全面的了解。函数
在讲参数传递前,先要理解变量在内存中的存放方式。ECMAScript变量有多是5种基本类型的值(Undefined,Null,Boolean,Number,String),也有多是引用类型值(Object),即对象。spa
对于值是5种基本类型的变量而言,变量是放在栈内存里的。由于这些变量占据的内存是固定的,这样存储便于迅速查寻变量的值。例如:设计
var name = "Nicholas",
city = "Beijing",
3d
age = 22;
指针
这些变量的存储结构为:code
而对于值是引用类型值的变量而言,是同时保存在栈内存和堆内存中的。例如:cdn
var obj1 = {name:"Nicholas"},
对象
obj2 = {name:"Greg"};
blog
在栈内存里没有直接存对象,而是存的对象在堆内存中的地址。对象的属性和方法都包含在对象里。
ip
了解了变量在内存中的存储方式后,还要理解变量赋值的过程。用一个变量向另外一个变量赋值时,基本类型值和引用类型值也会有所不一样。若是用一个变量向新变量赋基本类型值,会在变量上建立一个新值,而后把该值赋给为新变量分配的位置上。例如:
var num1 = 5,
num2 = num1,
num1 = 10;
alert(num2); //5
当用变量num1为num2赋值时,num2中也保存了值5。但这个5与变量num1中的5是相互独立的,互不影响。即使后来num1的值变为10,num2的值仍是5。
当从一个变量向另外一个变量复制引用类型的值时,一样也会把存储在变量对象中值复制一份到新变量分配的空间中。前面提到过,这个值其实是一个指针,而这个指针是指向存储在堆中的一个对象。因为这两个变量的值相同(即指针相同,指向同一个对象),因此改变一个变量的时,另外一个变量也会改变。例如:
var obj1 = new Object(),
obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name);//"Nicholsa"
obj2.name = "Greg";
alert(obj1.name);//"Greg"
首先是变量obj1保存了一个对象的新实例,当obj1的值赋给obj2时,其实是把obj1指向这个对象的地址赋给了obj2,而后obj2也指向这个新对象。当为obj1添加属性后,obj2也能访问这个属性,而且属性值是相同的。由于这两个变量指向的是同一个对象。
可是若是为obj2赋值后,又新建一个对象实例赋值给obj2,那么obj2将不在指向obj1。obj1和obj2将相互独立,互不影响。例如:
var obj1 = new Object(),
obj2 = obj1,
obj2 = new Object();//新建一个对象实例,将在堆内存中从新分配地址空间
obj1.name ="Nicholas";
alert(obj2.name); //undefined
obj2.name = "Greg";
alert(obj1.name); //"Nicholas"
讲完前面两点,能够进入正题了——JS中函数参数的传递方式。
函数参数传递的过程实际就是实参向形参复制值的过程。在向参数传递基本类型的值时,被传递(实参)的值会复制给一个局部变量(形参),形参值的变化不会对函数外的实参产生影响。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给形参。这时这个形参也指向了函数外的实参,所以这个形参的变化也会致使实参的变化。例如:
function addTen(num) {
num += 10;
return num;
}
var count = 20,
result = addTen(count);
alert(count);//20,形参值的变化不会影响实参
alert(result);//30
这里函数addTen()的参数num,其实是函数的局部变量。在调用函数时,变量count做为参数传递给函数。因为count的值是20,因此数值20被复制给参数num。在函数内部,这个参数被加了10,但这并不会影响函数外部的count变量。
当向参数传递的值为对象时,例如:
function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
person.name = "Greg";
setName(person);
alert(person.name);//"Nicholas"
这里首先是建立了一个对象,保存在变量person中,而且给变量的name属性赋值为"Greg"。而后这个变量被看成参数传递给函数setName的参数obj。在函数内部,obj和person指向同一个对象,由于传递的是对象的地址。因此给obj的name属性赋值后,也会改变person的name属性值。但若是在函数内部为obj新建一个对象实例,这个新对象实例会开辟新的内存空间,致使obj的地址和person不一样。此时,obj和person将指向两个不一样的对象,因此互不影响。例如:
function setName(obj){
obj.name = ""Nicholas;//这个obj和person指向的地址相同,即函数外person建立的对象。
obj = new Object();//新建实例对象,致使obj指向另外一个地址
obj.name = "Greg";
}
var person = new Object();
person.name = "Jhon";
setName(person);
alert(person.name);//"Nicholas"
再举个例子:
var obj1 = {
value:'111'
};
var obj2 = { value:'222'};
function changeStuff(obj){
obj.value = '333';
obj = obj2;
return obj.value;
}
var foo = changeStuff(obj1);
console.log(foo);// '222' 参数obj指向了新的对象
console.log(obj1.value);//'333'
整个过程能够用下图表示:
总结:JavaScript函数的参数都是按值传递的。基本类型值的传递是按值传递,引用类型值的传递也是按值传递,只不过这个值存放的是地址。