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