우선 얕은 복사와 깊은 복사에 대해 알기 전에 원시 자료형과 참조 자료형에 대한 이해가 필요하다. 원시 자료형
은 하나의 데이터를 담으면서 변수가 곧 데이터를 저장하는 메모리이고 string, number, boolean 등이 해당한다. 참조 자료형
은 여러개의 데이터를 담으면서 변수에는 데이터의 보관함(heap)의 주소
를 저장한다. 배열, 객체, 함수가 대표적인 참조 자료형이다.
얕은 복사는 참조(주소)값을 복사
하고 배열, 객체와 같은 참조 자료형
의 복사에서 볼 수 있다.
const obj = { a : 1 }
cont newObj = obj; // obj의 참조값을 할당
newObj.a = 2;
console.log(obj) // { a : 2}
console.log(obj === newObj) // true
newObj
객체에 obj
객체를 할당할 경우, 데이터의 값 자체가 아닌 데이터의 참조값(주소)를 할당
하게 되고 이를 참조 할당
이라고 한다. 이 경우 원본(obj
)과 복사본(newObj
)가 서로 heap의 주소를 공유하게 되어 newObj.a
값을 변경하면 obj.a
값 또한 변경된다.
깊은 복사는 데이터의 값 자체를 복사
하고 참조(주소)값은 복사하지 않는다. 원시 자료형
의 복사가 깊은 복사에 해당한다.
const a = 1;
const b = a;
b = 2;
console.log(a) // 1
console.log( a === b ) // false
변수b
에 변수a
를 할당 후 b
의 값을 재할당 해주었지만 a
의 값은 변하지 않는다.
객체와 배열과 같은 참조 자료형
의 경우에는 메서드를 이용해야 깊은 복사를 할 수 있다. 배열
이라면 slice()
, 객체 내에 객체를 포함하지 않는 객체
의 경우 Object.assign()
나 Spread syntax
등을 이용하면 깊은 복사를 할 수 있다.
Object.assign()
// 객체 내에 객체를 포함 하지 않음
const obj = { a : 1, b : 2 };
const copy = Object.assign({}, obj);
console.log(copy) // { a : 1, b : 2 }
copy.a = 2;
console.log(copy) // { a : 2, b : 2 }
console.log(obj) // { a : 1, b : 2 }
console.log(obj === copy) // false
// 객체 내에 객체를 포함
const obj = {
a: 2,
b: { c: 3 }
};
const copy = Object.assign({}, obj);
copy.b.c = 1;
console.log(obj) // { a: 2, b: { c: 1 } }
console.log(obj === copy) // false
console.log(obj.b.c === copy.b.c) // true
🚫 하지만 객체 내에 객체를 포함하는 객체
는 이 메서드를 통해 깊은 복사를 할 수 없다. 객체 자체에는 깊은 복사가 이루어 지지만, 객체 내의 객체는 얕은 복사가 이루어지기 때문에 완전한 깊은 복사가 아니다. Spread syntax
역시 마찬 가지이다.
JSON.stringify()
/ JSON.parse()
JSON
객체 메서드를 이용하면 완전한 깊은 복사
를 할 수 있다.
JSON.stringify()
메서드는 인수로 객체를 받고 객체를 문자열로 치환한다.
JSON.parse()
메서드는 문자열을 인수로 받고 문자열을 객체로 치환한다.
const obj = {
a: 1,
b: {
c: 2,
}
};
const newObj = JSON.parse(JSON.stringify(obj));
newObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 } }
console.log(obj.b.c === newObj.b.c); // false
하지만 이 방법 또한 문제가 있다. 객체의 값 중 함수를 포함하고 있을 경우
이 메서드를 사용하고 나면 undefined
로 처리된다는 점이다.
const obj = {
a: 1,
b: {
c: 2,
},
func: function() {
return this.a;
}
};
const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj.func); // undefined
cloneDeep()
사실 lodash 모듈
의 cloneDeep()
메서드를 이용하면 간단하게 깊은 복사를 할 수 있다. 이 메서드를 사용하기 위한 방법은 찾아보면 간단히 알 수 있다.
const lodash = require("lodash");
const obj = {
a: 1,
b: {
c: 2,
},
func: function () {
return this.a;
},
};
const newObj = lodash.cloneDeep(obj);
newObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === newObj.b.c); // false
이 외에도 재귀함수를 사용하는 방법 등이 있지만 cloneDeep()
메서드를 이용하면 간단히 구현할 수 있고, 실제로 흔히 사용하는 방법이라고 한다.