JavaScript的記憶體空間、賦值和深淺拷貝

劉樸發表於2019-04-16

JavaScript的記憶體空間

在JavaScript中,每一個資料都需要一個記憶體空間。記憶體空間分為兩種,棧記憶體(stack)與堆記憶體(heap)

棧是系統自動分配的記憶體空間,由系統自動釋放,堆則是動態分配的記憶體,大小不定不會自動釋放。

基礎資料型別

JavaScript中的基礎資料型別,這些值都有固定的大小,儲存在記憶體中,由系統自動分配儲存空間在棧記憶體空間的值,我們可以直接進行操作,因此基礎資料型別都是按照值訪問

在棧記憶體中的資料發生複製的行為時,系統會自動為新變數開闢一個新的記憶體空間,當複製執行後,兩個記憶體空間的值就互不影響,改變其中一個不會影響另一個

棧記憶體空間資料複製示例
var a = `I am variable a`;
var b = a; 
console.log(b); //`I am variable a`
b = `I am variable b`;
console.log(a); //`I am variable a`
console.log(b); //`I am variable b`
複製程式碼

引用資料型別

引用型別的值是儲存在記憶體中的物件,在JavaScript中我們不能直接操作物件的堆記憶體空間。因為引用型別的值都是按引用訪問的,所以在操作物件時,實際上是操作物件的引用而不是實際的物件。引用可以理解為儲存在棧記憶體中的一個地址,該地址指向堆記憶體中的一個實際物件

引用型別值的複製,系統會為新的變數自動分配一個新的棧記憶體空間這個棧記憶體空間儲存著與被複制變數相同的指標,儘管他們在棧記憶體中的記憶體空間的位置互相獨立但是在堆記憶體中訪問到的物件實際上是同一個,因此,當我們改變其中一個物件的值時,實際上就是改變原來的物件

棧記憶體空間儲存指標(地址),堆記憶體空間儲存實際的物件,我們通過變數訪問物件時,實際上訪問的是物件的引用(地址)

記憶體中的棧區域存放變數(基本型別的變數包括變數宣告和值)以及指向堆區域儲存位置的指標(引用型別的變數包括變數宣告和指向內容的指標)

var a = {
    name : `I am object a`,
    type : 'object'
}

var b = a;
console.log(b);
// {name: "I am object a", type: "object"}

b.name = `I am object b`;

console.log(a);
// {name: "I am object b", type: "object"}

console.log(b);

// {name: "I am object b", type: "object"}

複製程式碼

基本型別總結

基本資料型別

包括:null、undefined、number、string、boolean、symbol(es6)

存放位置:記憶體中的棧區域中

比較:值的比較,判斷是否相等,如果值相等,就相等。一般使用===進行比較,因為==會進行型別的轉換

拷貝:賦值(通過(=)賦值操作符 賦值),賦值完成後,兩個變數之間就沒有任何關係了,改變其中一個變數的值對另一個沒有任何影響

引用型別總結

引用資料型別

包括:陣列、物件、函式

存放位置:記憶體的棧區域中存放變數和指標,堆區域儲存實際的物件

比較:是引用的比較(就是地址的比較,變數在棧記憶體中對應的指標地址相等就指向同一個物件)判斷是否為同一個物件,示例如下

變數a和變數b的引用不同,物件就不是同一個物件
var a = {name:'Jay'};
var b = {name:'Jay'};
a===b //false
複製程式碼

我們對JavaScript中引用型別進行操作的時候,都是操作其物件的引用(儲存在棧記憶體中的指標)

賦值、深拷貝和淺拷貝 (Assignment, deep copy and shallow copy)

賦值:兩個變數的值(指標)都指向同一個物件,改變其中一個,另一個也會受到影響

所謂拷貝就是複製,通過複製原物件生成一個新的物件

淺拷貝:重新在堆記憶體中開闢一個空間,拷貝後新物件獲得一個獨立的基本資料型別資料,和原物件共用一個原物件內的引用型別資料,改變基本型別資料,兩個物件互不影響,改變其中一個物件內的引用型別資料,另一個物件會受到影響

var obj = {
    name: 'Jay Chou',
    age: 32,
    song:{
        name:'發如雪',
        year:2007
    }
}
var obj1 = obj;
function shallowCopy(obj){
    var scObj = {};
    for(var prop in obj){
        if(obj.hasOwnProperty(prop)){
            scObj[prop] = obj[prop]
        }
    }
    return scObj;
}
var obj2 = shallowCopy(obj);
console.log(obj === obj1,'obj === obj1','賦值');
console.log(obj === obj2,'obj === obj2','淺拷貝');
// true "obj === obj1" "賦值"
// false "obj === obj2" "淺拷貝"
console.log(obj.song === obj2.song);
//true
obj2.song.name='雙截棍';
obj2.name='Jay';
console.log(obj)
// {name: "Jay Chou", age: 32, song: {name:'雙截棍',year:2007}}
console.log(obj1);
// {name: "Jay Chou", age: 32, song: {name:'雙截棍',year:2007}}
console.log(obj2);
{name: "Jay", age: 32, song: {name:'雙截棍',year:2007}}
console.log(obj===obj1)
//true
console.log(obj===obj2)
//false
複製程式碼

深拷貝:不論是物件內的基本型別還是引用型別都被完全拷貝,拷貝後兩個物件互不影響

一種比較簡單實現方法是使用var dcObj = JSON.parse(JSON.stringify(obj))

var obj = {
    name: 'Jay Chou',
    age: 32,
    song:{
        name:'發如雪',
        year:2007
    }
}

var dcObj=JSON.parse(JSON.stringify(obj));

console.log(dcObj);
// {name: "Jay Chou", age: 32, song: {name:'發如雪',year:2007}}
console.log(dcObj.song === obj.song);
//false
dcObj.name='Jay';
dcObj.song.name='雙截棍';
console.log(obj);
// {name: "Jay Chou", age: 32, song: {name:'發如雪',year:2007}}
console.log(dcObj);
//{name: "Jay", age: 32, song: {name:'雙截棍',year:2007}}
複製程式碼

比較:賦值、深拷貝、淺拷貝

賦值:新物件仍然指向原物件,改變新物件的基本型別引用型別的值都會使原物件對應的值一同改變

淺拷貝:改變新物件基本型別的值不會使原物件對應的值一起改變,但是改變新物件引用型別的值使原物件對應的值一同改變

深拷貝:改變新物件基本型別引用型別的值,都不會影響原物件,兩者互相獨立,互不影響

相關文章