JavaScript深淺拷貝

棕小漸發表於2018-05-20

基本型別 & 引用型別

ECMAScript中的資料型別可分為兩種:

  • 基本型別:undefined,null,Boolean,String,Number,Symbol
  • 引用型別:Object,Array,Date,Function,RegExp等

不同型別的儲存方式:

  • 基本型別:基本型別值在記憶體中佔據固定大小,儲存在棧記憶體
  • 引用型別:引用型別的值是物件,儲存在堆記憶體中,而棧記憶體儲存的是物件的變數識別符號以及物件在堆記憶體中的儲存地址

不同型別的複製方式:

  • 基本型別:從一個變數向另外一個新變數複製基本型別的值,會建立這個值的一個副本,並將該副本複製給新變數
let foo = 1;
let bar = foo;
console.log(foo === bar); // -> true

// 修改foo變數的值並不會影響bar變數的值
let foo = 233;
console.log(foo); // -> 233
console.log(bar); // -> 1
複製程式碼
  • 引用型別:從一個變數向另一個新變數複製引用型別的值,其實複製的是指標,最終兩個變數最終都指向同一個物件
let foo = {
  name: 'leeper',
  age: 20
}
let bar = foo;
console.log(foo === bar); // -> true

// 改變foo變數的值會影響bar變數的值
foo.age = 19;
console.log(foo); // -> {name: 'leeper', age: 19}
console.log(bar); // -> {name: 'leeper', age: 19}
複製程式碼

深拷貝 & 淺拷貝

  • 淺拷貝:僅僅是複製了引用,彼此之間的操作會互相影響
  • 深拷貝:在堆中重新分配記憶體,不同的地址,相同的值,互不影響

總的來說,深淺拷貝的主要區別就是:複製的是引用還是複製的是例項

深淺拷貝的實現

看一看原生JavaScript中提供的一些複製方法究竟是深拷貝還是淺拷貝以及動手實現深拷貝。

淺拷貝

  • Array.prototype.slice()
let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
複製程式碼
  • Array.prototype.concat()
let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
複製程式碼

看起來Array的slice(),concat()似乎是深拷貝,再接著看就知道它們究竟是深拷貝還是淺拷貝:

let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
複製程式碼

slice()

同樣,對於concat()也進行驗證:

let a = [[1, 2], 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
複製程式碼

綜上, Array的slice和concat方法並不是真正的深拷貝,對於Array的第一層的元素是深拷貝,而Array的第二層 slice和concat方法是複製引用。所以,Array的slice和concat方法都是淺拷貝

深拷貝

  • JSON.parse()和JSON.stringify()
  1. JSON.stringify():把一個js物件序列化為一個JSON字串
  2. JSON.parse():把JSON字串反序列化為一個js物件
let obj = {
  name: 'leeper',
  age: 20,
  friend: {
    name: 'lee',
    age: 19
  }
};
let copyObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Sandman';
obj.friend.name = 'Jerry';
console.log(obj);
// -> {name: "Sandman", age: 20, friend: {age: 19,name: 'Jerry'}}
console.log(copyObj);
// -> {name: "leeper", age: 20, friend: {age: 19,name: 'lee'}}
複製程式碼

deep copy

綜上,JSON.parse()和JSON.stringify()是完全的深拷貝

  • 動手實現深拷貝 利用遞迴來實現對物件或陣列的深拷貝。遞迴思路:對屬性中所有引用型別的值進行遍歷,直到是基本型別值為止。
function deepCopy(obj) {
  if (!obj && typeof obj !== 'object') {
    throw new Error('error arguments');
  }
  // const targetObj = obj.constructor === Array ? [] : {};
  const targetObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    
    //只對物件自有屬性進行拷貝
    if (obj.hasOwnProperty(key)) {
      if (obj[key] && typeof obj[key] === 'object') {
        targetObj[key] = deepCopy(obj[key]);
      } else {
        targetObj[key] = obj[key];
      }
    }
  }
  return targetObj;
}
複製程式碼

相關文章