BACK
Featured image of post JavaScript的記憶體空間、賦值和深淺拷貝

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

當 JavaScript 建立事物(如物件、字串等)時會分配空間給值且自動釋放不再使用的值。後者的流程稱作為回收機制。這個自動化的回收流程是一個混亂的根源,它會使 JavaScript 的開發者 (或者其他高階語言的開發者) 產生可以不須理會「記憶體管理」的錯誤認知。

參考網站


JavaScript的記憶體空間

  • 在 JavaScript 中,每一個數據都需要一個記憶體空間。記憶體空間分為兩種,棧記憶體(stock)堆記憶體(heap)
  • 是系統自動分配的記憶體空間,由系統自動釋放,則是動態分配的記憶體,大小不定不會自動釋放。

基礎資料型別

  • JavaScript 中的基礎資料型別,這些值都有固定的大小,儲存在 棧記憶體中,由系統自動分配儲存空間,在棧記憶體空間的值,我們可以直接進行操作,因此基礎資料型別都是按照值訪問。
  • 棧記憶體中的資料發生複製的行為時,系統會自動為新變數開闢一個新的記憶體空間,當複製執行後,兩個記憶體空間的值就互不影響,改變其中一個不會影響另一個

棧記憶體空間資料複製示例

1
2
3
4
5
6
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 中我們不能直接操作物件的堆記憶體空間。因為引用型別的值都是按引用訪問的,所以在操作物件時,實際上是操作物件的引用而不是實際的物件。 引用可以理解為儲存在棧記憶體中的一個地址,該地址指向堆記憶體中的一個實際物件。
  • 引用型別值的複製,系統會為新的變數自動分配一個新的棧記憶體空間 這個棧記憶體空間,儲存著與被複制變量相同的指標,儘管他們在棧記憶體中的記憶體空間的位置互相獨立,但是在堆記憶體中訪問到的物件實際上是同一個,因此當我們改變其中一個物件的值時,實際上就是改變原來的物件。
  • 棧記憶體空間儲存指標(地址),堆記憶體空間儲存實際的物件,我們通過變數訪問物件時,實際上訪問的是物件的引用(地址)。
  • 記憶體中的棧區域存放變數(基本型別的變數包括變數宣告和值)以及指向堆區域儲存位置的指標(引用型別的變數包括變數宣告和指向內容的指標)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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的引用不同,物件就不是同一個物件

1
2
3
var a = {name:'Jay'};
var b = {name:'Jay'};
a===b //false

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


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

  • 賦值:兩個變數的都指向同一個物件,改變其中一個,另一個也會受到影響。
  • 所謂拷貝就是複製,通過複製原物件生成一個新的物件。

淺拷貝

  • 重新在堆記憶體中開闢一個空間,拷貝後新物件獲得一個獨立的基本資料型別 資料,和原物件共用一個原物件內的引用型別 資料,改變基本型別 資料,兩個物件互不影響,改變其中一個物件內的引用型別 資料,另一個物件會受到影響。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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}}

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

賦值新物件仍然指向原物件,改變新物件的基本型別引用型別的值都會使原物件對應的值一同改變。
淺拷貝改變新物件基本型別的值不會使原物件對應的值一起改變,但是改變新物件引用型別的值會使原物件對應的值一同改變。
深拷貝改變新物件基本型別引用型別的值,都不會影響原物件,兩者互相獨立,互不影響。

comments powered by Disqus