深入解析js中基本資料型別與引用型別,函式引數傳遞的區別
ECMAScript的資料有兩種型別:基本型別值和引用型別值,基本型別指的是簡單的資料段,引用型別指的是可能由多個值構成的物件。
Undefined、Null、Boolean、Number和String是值型別,其他都是引用型別。其他語言String是以物件的形式表示,ECMAScript放棄了這一傳統。
記憶體中的儲存區域
值型別儲存在棧中,引用型別儲存在堆中。記憶體中是分為兩個區域的,一個是棧:它就是專門存放值型別的,但是它有一定的儲存空間,只能存放基本資料型別的資料和物件型別的引用地址也叫雜湊碼。儲存在棧裡面的基本資料型別的值都是有最大值和最小值的,不能超出它的預設範圍;二就是堆:它的儲存空間大,是用來儲存“陣列型別”和“物件類”的資料的。儲存在堆裡的引用型別資料是沒有固定大小的,比如說一個物件型別的資料,你可以往裡面存放一個字元、兩個字元·····更多,不管你存多少它都會把你存放的資料在記憶體的堆裡面開闢一塊空間來儲存,在棧裡面開闢一塊空間來存放引用地址
複製變數值
- 複製基本型別值
會在棧上重新分配一個記憶體空間,來存當前賦值的變數,這兩個變數可以參與任何操作而不會相互影響。
var name1 = 'kenny';
var name2 = name1;
name2 // 'kenny'
name2 = 'wukongyun';
name1 //'kenny'
- 複製引用型別值
將儲存在變數物件中的值複製一份放到新變數分配的空間中(新變數的指標儲存在棧上),複製的實際上是一個指標,而這個指標指向儲存在堆中的一個物件。兩個變數實際上引用的是同一個物件。改變其中一個變數,就會影響另一個變數。
var obj1 = {name: 'kenny' };
var obj2 = obj1;
obj1.name = 'kongyun';
obj2.name // 'kongyun'
引數的傳遞
ECMAScript所有的函式的引數都是按值傳遞的。函式外部的值賦值給函式內部的引數,與一個變數複製到另一個變數一樣。基本型別值的傳遞和基本型別一樣,引用型別的傳遞和引用型別的複製一樣。
function addTen(num) {
num +=10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(count); //20沒有變化
console.log(result);// 30
引用型別也是按值傳遞
function setName(obj) {
obj.name = 'kenny';
obj = new Object();
obj.name = 'kongyun';
}
var person = new Object();
setName(person);
console.log(person.name); // 'kenny'
即使在函式內部修改了引數的值,但原始的引用(person物件,儲存在堆上)仍保持不變。具體傳遞的obj不是指標而是指標引用的物件(副本copy)。實際上,當在函式內部重寫obj時,這個變數的引用的就是一個區域性物件了,而這個區域性物件會在函式執行完畢後立即被銷燬。
類似於這種例子 - -
var a = [1, 2];
var b = a;
a = {a:1, b:2};//雖然a改變了,但是b依然沒變,值傳遞,複製了個指標
擴充套件:值傳遞與引用傳遞
- 值傳遞:call by value
- 引用傳遞:call by Call by reference
值傳遞和引用傳遞,屬於函式呼叫時引數的求值策略(Evaluation Strategy),這是對呼叫函式時,求值和傳值的方式的描述,而非傳遞的內容的型別(內容指:是值型別還是引用型別,是值還是指標)。值型別/引用型別,是用於區分兩種記憶體分配方式,值型別在呼叫棧上分配,引用型別在堆上分配。一個描述記憶體分配方式,一個描述引數求值策略,兩者之間無任何依賴或約束關係。
區別 | 值傳遞 | 引用傳遞 |
---|---|---|
根本區別 | 會建立副本(copy) | 不建立副本 |
所以 | 函式中無法改變原始物件 | 函式中可以改變原始物件 |
對於值傳遞,無論是值型別還是引用型別,都會在呼叫棧上建立一個副本,不同是,對於值型別而言,這個副本就是整個原始值的複製。而對於引用型別而言,由於引用型別的例項在堆中,在棧上只有它的一個引用(一般情況下是指標),其副本也只是這個引用的複製,而不是整個原始物件的複製。
這便引出了值型別和引用型別(這不是在說值傳遞)的最大區別:值型別用做引數會被複制,但是很多人誤以為這個區別是值型別的特性。其實這是值傳遞帶來的效果,和值型別本身沒有關係。只是最終結果是這樣。