JavaScript参数传递的深刻理解

今天看到《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函数的参数都是按值传递的。基本类型值的传递是按值传递,引用类型值的传递也是按值传递,只不过这个值存放的是地址。

相关文章
相关标签/搜索