얕은 복사
는 주소값을 복사하기 때문에 참조하고 있는 실제 값은 같습니다.
깊은 복사
는 실제 값을 메모리 공간에 복사하기 때문에 참조하고 있는 실제 값이 다릅니다.
1️⃣ Object.assign()
const obj = { a: 1 };
const copyObj = Object.assign({}, obj);
copyObj.a = 2;
console.log(obj); // { a: 1 }
console.log(obj === copyObj); // false
위 예제는 copyObj라는 객체를 Object.asign() 메소드를 사용하여 생성한 코드이다.
copyObj.a 값을 변경하여도 기존의 obj.a의 값이 바뀌지 않았다.
서로의 객체를 비교해도 false가 뜨며 서로 참조값이 다른 것을 알 수 있다.
📍 Object.assign()은 2차원 객체를 복사하였을 때 깊은 복사가 이루어지지 않는다 !
const obj = {
a: 1,
b: { c: 2 },
}
const copyObj = Object.assign({}, obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj.b.c === copyObj.b.c); // true
위 예제는 obj라는 2차원 객체를 copyObj에 복사하고 copyObj.b.c 값을 변경한 예제이다.
기존 obj 객체를 출력해보면 Object.assign() 메서드를 사용하여 객체를 복사하였음에도 불구하고 값이 변경됐다.
이는 복사된 하위 객체 { c: 2 }도 결국 객체이기 때문에 얕은 복사(Shallow Copy)가 된 것이다.
그래서 Object.assign() 메서드는 1차원 객체만 깊은 복사(Deep Copy)해야하며, 전개 연산자(Spread Operator)도 마찬가지이다.
2️⃣ 전개 연산자(Spread Operator)
const obj = {
a: 1,
b: { c: 2 },
};
const copyObj = {...obj};
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj.b.c === copyObj.b.c); // true
전개 연산자(Spread Operator)도 Object.assign()과 마찬가지로 2차원 객체는 얕은 복사(Shallow Copy)가 되는 것을 확인할 수 있다.
3️⃣ JSON의 stringify()와 parse()
- stringify() 문법: JSON.stringify() 메소드는 객체를 인수로 받으며 받은 객체는 문자열로 치환
- parse() 문법: JSON.parse() 메소드는 문자열을 인수로 받으며 받은 문자열은 객체로 치환
const obj = {
a: 1,
b: { c: 2 },
};
const copyObj = JSON.parse(JSON.stringify(obj));
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 } }
console.log(obj.b.c === copyObj.b.c); // false
위 예제는 obj 객체를 JSON.stringify() 메소드를 이용하여 문자열로 변환한 뒤
다시 JSON.parse() 메소드를 통해 객체로 변환한 예제이다.
2차원 객체도 깊은 복사(Deep Copy)가 되긴 하지만 2가지 문제가 있다.
1) 다른 방법에 비해 성능이 느리다.
2) JSON.stringify() 메소드는 함수를 만났을 때 undefined로 처리된다.
아래 예제 코드를 확인해보자.
const obj = {
a: 1,
b: { c: 2 },
func: function() {
return this.a;
}
};
const copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj.func); // undefined
복사된 copyObj는 func가 없고 undefined로 출력되고 있다.
4️⃣ 커스텀 재귀 함수
객체의 깊은 복사(Deep Copy)를 위해 직접 커스텀 재귀 함수를 구현하는 방법도 있다.
const deepCopy = (obj) => {
if (obj === null || typeof obj !== 'object') return obj;
let copy = {};
for (let key in obj) {
copy[key] = deepCopy(obj[key]);
}
return copy;
};
const obj = {
a: 1,
b: { c: 2 },
func: function () {
return this.a;
}
};
const copyObj = deepCopy(obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false
deepCopy() 함수로 obj 객체를 매개변수로 보낸 다음 인수값이 객체가 아닌 경우엔 그냥 반환하며,
객체인 경우 객체의 값 만큼 루프를 돌고 재귀를 호출하여 복사된 값을 반환한다.
복사된 copyObj 객체를 확인해보면 2차원 객체의 값도 깊은 복사(Deep Copy)가 되었으며, 객체의 함수도 제대로 출력되는 것을 확인할 수 있다.
5️⃣ lodash 라이브러리의 cloneDeep()
lodash 라이브러리의 cloneDeep() 메소드를 이용해 객체의 깊은 복사(Deep Copy)가 가능하다.
해당 라이브러리를 설치했다는 가정 하에 아래 코드를 확인해보자.
const obj = {
a: 1,
b: { c: 2 },
func: function () {
return this.a;
}
};
const copyObj = lodash.cloneDeep(obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false
lodash 라이브러리의 cloneDeep() 메소드를 이용하면 깊은 복사를 간편하게 구현할 수 있다.
💟 참고자료