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函數的參數都是按值傳遞的。基本類型值的傳遞是按值傳遞,引用類型值的傳遞也是按值傳遞,只不過這個值存放的是地址。

相關文章
相關標籤/搜索