객체를 생성하여 그냥 그 안의 데이터를 변경하거나 얕은 복사(shallow copy)로 데이터를 변경하면, 본체가 변경되기 때문에 깊은 복사(deep copy를 아는 것은 필수이다. 오늘 배운 객체를 깊은 복사 하는 방법을 기록해보고자 한다.
객체를 깊은 복사하는 방법 3가지를 기록하고자 한다.
가장 마지막에 최근에 알게된 가장 쉬운 딥카피 방법을 소개한다.
const original = { a: 1, b: 2, c: { d: 4, e:5 } };
const deepCopy = { ...original, c: { ...original.c } };
객체 안의 객체가 있을 경우 또 ... 해줘야하는 번거로움이 있지만 이 방법이 제일 보기 쉽고 실제로 하기도 쉬운 것 같다.
const original = { a: 1, b: 2, c: { d: 4, e:5 } };
const deepCopy = JSON.parse(JSON.stringify(original));
이 방법 또한 객체를 JSON.stringify하여 문자열로 바꿔준 다음 다시 JSON.parse으로 새로운 객체로 만드는 방법이다. 이 또한 굉장히 쉬워보이긴한다.
const deepCopyObject = object => {
let result = {};
if (typeof object === "object" && object !== null) {
for (let x in object) {
result[x] = deepCopyObject(object[x]);
}
} else {
result = object
}
return result
}
const original = { a: 1, b: 2, c: { d: 4, e:5 } };
const copyObj = deepCopyObject(original);
개인적으로 가장 어렵고 지금도 이해하려고 하면 잘 안되는 재귀함수 사용이다. 이 부분을 제대로 이해하려면 코드를 최대한 쪼개서 순서대로 확인해봐야겠다. 이해하기 쉽게 객체는 간단한걸로 만들어서 해봐야겠다.
* const original = { a: 1, b: 2 }
* const copyObj = deepCopyObject(original)
에서
1. const copyObj = deepCopyObject({ a: 1, b: 2 })
// original이란 객체를 넣어준다.
2. const deepCopyObject = { a: 1, b: 2 } => {
let result = {};
if (typeof { a: 1, b: 2 } === "object" && { a: 1, b: 2 } !== null) {
for (let a in { a: 1, b: 2 }) {
result[a] = deepCopyObject(object[a]);
}
} else {
result = object
}
return result
}
// 2의 두 번째 줄
2-1. 위의 함수에서 result라는 객체를 생성하고,
// 2의 세 번째 줄
2-2. if 문에서 객체가 맞고 동시에 null이 아니므로 반복문에 들어가면
// 2의 반복문 안, 다섯 번째 줄
2-3. result[a] = deepCopyObject(1)이 된다.
deepCopyOject(1)을 다시 해보면...
---
const deepCopyObject = 1 => {
let result = {};
if (typeof 1 === "object" && 1 !== null) {
for (let x in object) {
result[x] = deepCopyObject(object[x]);
}
} else {
result = 1
}
return result
}
--- 에서
2-4. deepCopyObject(1), 이 매개변수가 1이므로,
2-5. if문의 조건을 만족하지 않음으로 else문으로 이동
2-6. result = 1이고, result를 return하므로 1을 return하게 된다.
---
다시 2-3으로 돌아와서 보면
2-7. result[a] = 1, 즉, result = { a: 1 }
3. 다시 2-2의 반복문으로 돌아가서 result[b] = deepCopyObject{original[b])
3-1. result[b] = deepCopyObject(2)
3-2. deepCopyObject(2)를 보면...
---
const deepCopyObject = 2 => {
let result = {};
if (typeof 2 === "object" && 2 !== null) {
for (let x in object) {
result[x] = deepCopyObject(object[x]);
}
} else {
result = 2
}
return result
}
--- 에서
3-3. 2번과 마찬가지로 if문을 건너뛰고, else문으로 가면 2를 return하게 되므로
3-4. result[b] = 2가 된다. 즉, result = {a:1, b:2}
4. 반복문이 끝났으므로 빠져나와 result값을 return 하면 CopyObj = deepCopyObject(original)
즉,
CopyObj = { a : 1, b : 2 }
으로 성공적으로 깊은 복사가 완료된것을 볼 수 있다.
재귀함수 누가 만들었어! 😢
내 생각에는 이 방법이 제일 쉬운것같다. 물론, 객체 안에 객체가 더 있으면 수동으로 spread문법을 한 번 더 써줘야하는 번거로움이 있긴 하지만, 코드를 읽기에도 편하고 쓰기에도 나쁘진 않은것같다. 하지만 내장객체가 있을 때, 수동으로 spread문법을 한 번 더쓰는걸 깜빡한다면, 사고가 나는것이다. 에러가 나도 어디에서 나는지 모르는 끔찍한 경험을 할 수도 있을 것같다.
이 방법은 객체를 JSON 문자열로 변환하는 간단한 방법이다. JSON.stringify는 객체를 깊이 탐색하여 중첩된 객체나 배열도 모두 문자열로 변환하기 때문에 깊은 복사가 유용하다. 또, 문자열로 변환된 JSON은 데이터를 저장하거나 전송하기에 적합한 형식이므로 다른 플랫폼이나 시스템에서도 쉽게 사용 가능하다는 장점이 있다.
단점으로는 문자열을 객체로 변환할 때, 일부 데이터 유형의 복원에 제한이 있을 수 있다는 것이다. 예를 들어 날짜 객체는 JSON으로 직렬화되지 않으므로 JSON.parse를 사용하여 원래의 날짜 객체를 복원할 수 없다. 또 보안 상의 이슈가 있을 수도 있다. 악의적인 JSON 데이터를 파싱하면 코드 인젝션 등의 보안 취약점이 발생 가능하다.
객체나 배열과 같은 복합 데이터 구조의 완벽에 가까운 깊은 복사가 가능하다. 이는 원본 데이터를 변경해도 복사본에는 영향을 주지 않으므로 데이터의 무결성을 보장할 수 있다.
하지만 재귀 함수는 호출 스택을 사용하므로 데이터의 크기가 큰 경우 스택 오버플로우가 발생할 수도 있다. 또 종료 조건을 명확히 정의해야 무한 재귀에 빠지지 않고 올바르게 깊은 복사를 할 수 있다.
또, 가장 마이너스가 되는 요소는 다른 방법에 비해 너무 복잡하고 사용자가 수동으로 코드를 만들어야 한다는 것이다.
이로써 객체의 깊은 복사 3가지 방법을 찾아봤다. 재귀함수는 더 공부해야겠다!
방금 알게된 가장 쉬운 진짜 이렇게 쉬워도 되나 하는 딥카피 방법을 알아냈다.
const a = {a:1, b:2, c: {a:1, b:2, c:{a:1,b:3, c:[1,2,3]}}}
const b = structuredClone(a)
하면 그냥 끝이다. 너무 허무할 정도로 끝나 버린다.