iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
自我挑戰組

前端菜雞_賀周歲成長日誌系列 第 11

深拷貝、淺拷貝的說明及實驗

  • 分享至 

  • xImage
  •  

前言

今日介紹:深拷貝、淺拷貝。在實作上應該經常碰到,尤其是當需要處理龐大後端傳來的資料時。


深拷貝、淺拷貝

淺拷貝

當新舊陣列,會因為資料使用的地址相同,導致任何一方有改變時會影響對方,就是淺拷貝。

通常物件內第一層是是獨立的;但如果是物件,新舊的第二層物件會指向相同的地址、有相同的值,就會互相影響。

Original Object data 與 Cloned Object data有任何一層的資料地址相同,背後指向的值相同,兩物件的操作會相互影響。

以下方法即為各種只複製「值」的淺拷貝方法。

1. 直接複製,直接讓 clonedData = originalData

直接複製過去,內容會是一樣的沒有問題。

  • 狀況一 :改變 newData 的值,影響到了原 data。
  • 狀況二:改變原 data 的值,影響到了 newData。
let data = {
    layerOne: 1, 
    obj: {layerTwo: 10,}
}
let newData
newData = data

console.log(data === newData)   // true
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20
console.log(data === newData)   // true
// data = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
console.log(data === newData)   // true
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }

2. 手動複製

一個個以原陣列來賦值,內容會一樣。

  • 狀況一 :改變 newData 的值,第一層原 data沒被影響到,但影響到了第二層原 data (物件中的物件)。
  • 狀況二:改變原 data 的值,第一層newData沒被影響到,但影響到了第二層 newData (物件中的物件)。
let data = {
  layerOne: 1, 
  obj: {layerTwo: 10,}
}
let newData
// 手動複製
newData = {
  layerOne: data.layerOne,
  obj: data.obj,
}

console.log(data === newData)   // false
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20
// data第一層沒被影響,第二層被影響
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 20,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
// newData第一層沒被影響,第二層被影響
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 30,}
// }

3. Array.slice()、Array.from()

使用種種 array 的方法

  • data 以及 newData 物件的第一層不被影響,第二層則會被影響。
// slice
let a = [1, 2, { layerTwo:3 }];
let b ;
b = a.slice(0);
console.log(a);			// [1, 2, { layerTwo:3 }]
console.log(b);			// [1, 2, { layerTwo:3 }]
console.log(b === a);	// false

// from
let a = [1, 2, { layerTwo:3 }];
let b ;
b = Array.from(a);
console.log(a);			// [1, 2, { layerTwo:3 }]
console.log(b);			// [1, 2, { layerTwo:3 }]
console.log(b === a);	// false

//-------------------------------------//情況一
a[0] = 2
a[2].layerTwo = 4
// b 第一層沒被影響,第二層被影響
// a = [ 2, 2, {layerTwo: 4,}]
// b = [ 1, 2, {layerTwo: 4,}]
//-------------------------------------//情況二
b[0] = 2
b[2].layerTwo = 5
// a 第一層沒被影響,第二層被影響
// a = [ 1, 2, {layerTwo: 5,}]
// b = [ 2, 2, {layerTwo: 5,}]

4. Object.assign({},originalData)

使用 assign 方法

  • data 以及 newData 物件的第一層不被影響,第二層則會被影響。
let data = {
    layerOne: 1, 
    obj: {layerTwo: 10,}
}
let newData
// Object.assign
newData = Object.assign({}, data);

console.log(data === newData)   // false
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20
// data第一層沒被影響,第二層被影響
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 20,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
// newData第一層沒被影響,第二層被影響
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 30,}
// }

5. ES6的展開符 Spread operator

使用 ES6的展開符 來複製

  • data 以及 newData 物件的第一層不被影響,第二層則會相互影響。
let data = {
    layerOne: 1, 
    obj: {layerTwo: 10,}
}
let newData
// Spread operator
newData = {...data};
console.log(data)   
console.log(newData)  

console.log(data === newData)   // false
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20
// data第一層沒被影響,第二層被影響
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 20,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
// newData第一層沒被影響,第二層被影響
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 30,}
// }

深拷貝

資料不會互相影響。就算是深層的物件,因為它們有各自對應的地址,所以不會受其他物件的改變被影響。

Original Object data 與 Cloned Object data 兩方完全獨立,每一的資料位址都不同,不會互相影響的深層物件

1. JSON.parse(JSON.stringify())

let data = {
  layerOne: 1, 
  obj: {layerTwo: 10,}
}
let newData
// JSON.parse(JSON.stringify())
newData = JSON.parse(JSON.stringify(data));

console.log(data === newData)   // false
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20 
console.log(data === newData)   // false
// data第一層、第二層都沒被影響
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
console.log(data === newData)   // false
// newData第一層、第二層都沒被影響
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

使用JSON.parse(JSON.stringify()) 方法會有些要注意的地方,後續補充上來

2. 用迴遞函式(for...、for...in...)寫一個深拷貝用的函數

let data = {
    layerOne: 1, 
    obj: {layerTwo: 10,}
}
let newData = {}
// 自己寫深拷貝函數
function deepCopy(dataNew, dataOld){
    for (let key in dataOld) {
        if(typeof dataOld[key] === 'object'){
            dataNew[key] = {};
            deepCopy(dataNew[key], dataOld[key])
        }else{
            dataNew[key] = dataOld[key];
        }
    }
}

deepCopy(newData, data)

console.log(data === newData)   // false
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }

//-------------------------------------//情況一
newData.layerOne = 2
newData.obj.layerTwo = 20 
// data第一層、第二層都沒被影響
// data = {
//     layerOne: 1, 
//     obj: {layerTwo: 10,}
// }
// newData = {
//     layerOne: 2, 
//     obj: {layerTwo: 20,}
// }

//-------------------------------------//情況二
data.layerOne = 3
data.obj.layerTwo = 30
// newData第一層、第二層都沒被影響
// data = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }
// newData = {
//     layerOne: 3, 
//     obj: {layerTwo: 30,}
// }

做個乾淨的小整理:

  1. 淺拷貝(shallow copy)

Original Object data 與 Cloned Object data有任何一層的資料地址相同,背後指向的值相同,兩物件的操作會相互影響

淺拷貝方式

  • 直接讓 clonedData = originalData
  • 手動複製
  • Object.assign({},a);const cloneData = Object.assign({}, originalData)
  • ES6的展開符(Spread operator);const cloneData = {...originalData}
  • 部分Array方法,如:slice()、from() 等
  1. 深拷貝(deep copy)

Original Object data 與 Cloned Object data 兩方完全獨立,每一的資料位址都不同,不會互相影響的深層物件

深拷貝方式

  • 先用JSON.stringify()把物件轉字串,再用JSON.parse()把字串轉物件。注意有些值經過JSON.stringify/parse處理後會發生改變。
  • 用第三方函式庫(例如Lodash cloneDeep())
  • 用迴遞函式(for...、for...in...)

結語

今天用簡單的示例介紹了淺拷貝與深拷貝的幾種方法,我把自己的微小經驗與對其他優秀網站的理解做成筆記,雖然蠻多重複的地方(實驗結果都相同的部分),但因為我當初在理解這塊,是反覆看了幾次才明白深拷貝與淺拷貝的差異的,所以希望以上的超基本實驗對入門新手是友善的(也是想給金魚腦的自己再回來看時一目瞭然)。

今天就到這,如有說明不周或錯誤的地方,還請多留言討論(鞠躬)。

題外話:當初在工作上經常用到,但真正認真去搞清楚其中差異,其實是為了面試(小聲說)。

參考

for...、for...in...
https://blog.csdn.net/gang544043963/article/details/107196754
深淺拷貝。解釋很清楚!
https://www.programfarmer.com/articles/2021/javascript-shallow-copy-deep-copy
拷貝值
https://eudora.cc/posts/210430/
二維陣列轉一維解法
https://blog.camel2243.com/2016/09/02/javascript-二維陣列轉換為一維陣列/


上一篇
一些小功能:String number array object (四個方法複習)
下一篇
關於運算子
系列文
前端菜雞_賀周歲成長日誌31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言