賦值、淺拷貝與深拷貝

王振宇發表於2019-03-26

前面我們講過,基本資料型別存放的棧中,引用資料型別存放在堆中,指向引用資料型別的變數儲存在棧中,它儲存著指向堆中對應引用資料型別的記憶體地址(對以上問題不瞭解的朋友可以檢視之前的一篇文章《JavaScript之記憶體空間》),由此看如下程式碼:

let a = 'a';
let b = a;
let obj1 = {
  name:'obj.name'
}
let obj2 = obj2;複製程式碼

首先宣告瞭變數a並賦值'a',然後宣告b = a,這個時候屬於賦值,即建立變數b,並賦值'a';

接下來宣告物件obj1並賦值,此時在對中建立了物件{name:'obj.name'},然後宣告obj2 = obj1,

這個時候只是把obj1中儲存的指向物件{name:'obj.name'}的記憶體地址賦給了obj2,並不會再建立一個物件{name:'obj.name'}

那麼接下來看如下程式碼:

let xm = {
  name:'小明',
  hobby:['足球','籃球']
}
let xz = xm;
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
xz.name = '小張';
console.log(xm);
// {
//   name:'小張',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小張',
//   hobby:['足球','籃球']
// }複製程式碼

因為let xz = xm只是把xm儲存的物件的記憶體地址賦給了xz,所以當通過xz.name = '小張'改變了這個物件的時候,列印xm和xz拿到的都是改變後的物件,那麼如何複製一個物件呢?

可能你會想到Object.assign()

Object.assign() 方法用於將所有可列舉屬性的值從一個或多個源物件複製到目標物件。它將返回目標物件。  ---- (MDN)

那我們修改上面的程式碼如下:

let xm = {
  name:'小明',
  hobby:['足球','籃球']
}
let xz = Object.assign({},xm);
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
xz.name = '小張';
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小張',
//   hobby:['足球','籃球']
// }
複製程式碼

可能你會覺得還不錯嘛,那我們再修改一下:

let xm = {
  name:'小明',
  hobby:['足球','籃球']
}
let xz = Object.assign({},xm);
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
xz.name = '小張';
xz.hobby.push('乒乓球');
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球','乒乓球']
// }
console.log(xz);
// {
//   name:'小張',
//   hobby:['足球','籃球','乒乓球']
// }
複製程式碼

你會發現,不對啊,我明明只想給xz新增愛好,xm的愛好怎麼也多了?

下面我們正式引出淺拷貝和深拷貝

淺拷貝

其實淺拷貝就像我們上面說的變數賦值,當物件的屬性值為基本資料型別,那麼拷貝的就是基本資料型別的值,如果物件的屬性值為引用資料型別,那麼拷貝的就是記憶體地址,上面的程式碼可以用下圖更形象的解釋:

賦值、淺拷貝與深拷貝

所以,類似於Object.assign()屬於淺拷貝

深拷貝

理解了淺拷貝,深拷貝的概念就呼之欲出了,所謂深拷貝,即不管物件的屬性是基本資料型別還是引用資料型別,都會進行拷貝,所以拷貝前後的兩個物件是相互獨立,互不影響的。

上面例子實現深拷貝程式碼如下:

let xm = {
  name:'小明',
  hobby:['足球','籃球']
}
let xz = JSON.parse(JSON.stringify(xm));
xz.name = '小張';
xz.hobby.push('乒乓球');
console.log(xm);
// {
//   name:'小明',
//   hobby:['足球','籃球']
// }
console.log(xz);
// {
//   name:'小張',
//   hobby:['足球','籃球','乒乓球']
// }
複製程式碼

JSON.parse(JSON.stringify(obj))存在以下幾個問題:

let obj1 = {
  name:'obj',
  a:undefined,
  b:/\d{6}/g,
  c:function(){
    console.log(this)
  },
  d:new Date(),
  s:Symbol(123)
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2);
// {
//   name: "obj",
//   b: {},
//   d: "2019-03-26T13:51:45.158Z"
// }複製程式碼

可見對於undefined,函式,Symbol會直接忽略

對於new Date()轉換後結果不正確(可轉為時間戳或者字串)

對於正則轉換為{}

再看一種情況:

let obj1 = {
  a:{name:'a'}
}
obj1.b = obj1.a;
obj1.b.c = obj1.a;
console.log(obj1);
let obj2 = (JSON.parse(JSON.stringify(obj1)));
console.log(obj2);
// Uncaught TypeError: Converting circular structure to JSON
複製程式碼

可見對於迴圈引用,會報錯

下篇文章講如何手動實現一個深拷貝,有興趣的朋友歡迎關注

如果有錯誤或者不嚴謹的地方,請給予指正,十分感謝!

相關文章