javsacript를 처음 코딩을 하다보면 누구나 한번쯤 복사에서 멘붕이 온다. 분명히 object를 복사했다고 생각했는데.. 엉뚱한 값이 나오기때문이다. 이는 복사를 잘못해서인데, javscript의 복사개념을 잘 알고 구분해서 하면 이런 실수는 피할 수 있다.
자바스크립트에는 크게 두가지 복사가 있다. 객체의 참조(주소)를 복사하는 얕은 복사(Shallow copy)와, 객체를 모두 복제해서 새로운 객체를 만드는 깊은 복사(Deep copy)이다. 얕은 복사를 하면 복사본을 변경하면 원본도 변경이 되지만, 깊은 복사를 하면 복사본을 변경해도 원본은 변경되지 않는다.
예시를 위해 member라는 변수를 생성했다. 이렇게 변수를 만들면 객체는 메모리를 할당받아
오른쪽처럼 메모리주소(reference)와 데이터(value)를 갖게된다. member가 'member: hana'처럼 원시형이었다면 데이터에 값(hana)이 바로 저장되었겠지만 member는 객체이기때문에 주소만 참조해서 가지고 있다. 그래서 member가 가지고 있는 'x0007'라는 메모리를 주소를 찾아가면 member 값인 id와 type을 알 수 있다. 그런데 여기서 문제는 type도 객체라 'x0009'라는 주소만 가지고 있다. 그러면 또 type의 주소를 따라가면 값을 받아올 수 있다.
얕은 복사에는 ①참조복사와 ②객체복사가 있다.
참조복사는 객체의 주소(reference)만 복사해서, 원본 객체와 복사본 객체가 같은 객체를 바라보는 복사이다. 그래서, 복사본 객체를 변경하면 원본 객체도 같이 변경된다.
🧨 QUIZ. 만약, referenceCopy.id = 2로 변경한다면?
→ referenceCopy.id 가 바라보는 x0007의 데이터가 변경되어 referenceCopy.id = 2, member.id = 2 모두 값이 동일하게 변경된다
스프레드 연산자를 이용한 복사({...object})로 객체의 내용을 모두 복사해서 새로운 객체를 만든다. 그래서, 복사본 객체를 변경하더라도 원본 객체는 변경되지 않는다. 단, 복사본 안에 객체가 있다면 그건 주소만 복사한다.
🧨 QUIZ. 만약, objectCopy.id = 3로 변경한다면?
→ objectCopy.id 가 바라보는 x0011의 데이터가 변경되어 objectCopy.id = 3이 되고, referenceCopy.id와 member.id는 여전히 2이다
만약, objectCopy.type.premium = false로 변경하면?
→ objectCopy.type.premium 가 바라보는 x0009의 데이터가 변경되dj objectCopy.type.premium === referenceCopy.type.premium === member.type.premium === false로 모두 같다
얕은 복사를 하게되면 객체의 값이 복사되는 것이아니라 객체가 저장된 메모리의 주소값이 복사된다. 이때문에 원본 객체를 수정하면 해당 객체를 참조하는 복사된 객체도 변하게 되는데 이를 방지하는 것이 '깊은 복사'이다. 깊은 복사는 객체의 모든 속성과 값을 복사해서 새로운 메모리 공간에 저장하기 때문에 원복 객체를 수정해도 복사된 객체는 변하지 않는다.
JSON.stringify() 함수는 객체나 배열을 JSON 형식의 문자열로 변환하는 함수이고, JSON.stringify()를 이용하여 객체나 배열을 문자열로 변환하는 함수이다. 이 두 함수를 사용하면 원본 객체의 영향을 받지 않는 완전히 새로운 객체를 만들 수 있다.
let member = {id: 1, name: 'Tom', membership: {level: 'premium'}};
let copyJSON = (JSON.stringify(member));
// {"id":1, "name": "Tom", "membership": {"level": "premium"}}
let copyObj = JSON.parse(copyJSON)
// {id: 1, name: 'Tom', membership: {level: 'premium'}};
member.name = 'Hana';
member.membership.level = 'vip';
console.log('member: ', member);
// {id: 1, name: 'Hana', membership: {level: 'vip'}};
console.log('copiedObj: ', copyObj);
// {id: 1, name: 'Tom', membership: {level: 'premium'}};
그러나 이 방법에는 한가지 문제점이 있다. JSON.stringify() 함수를 이용하면 모든 속성이 '문자열'로 변환되기 때문에, Value가 함수나 undefined일 경우에는 값이 손실될 수 있다.
종료 조건을 만족할 때까지 객체의 함수 안에서 함수를 반복적으로 사용하여 모든 속성을 복사해 새로운 객체를 반환하는 방법이다. 결과 값을 출력하면 원복객체인 memver와 copiedObj의 객체 value까지 잘 복사된 걸 볼 수 있다.
function deepCopy(obj) {
// 종료 조건
if (typeof obj !== 'object' || obj === null) {
return obj;
}
const copy = {};
Object.keys(obj).forEach((key) => {
// 함수내에서 자기 자신을 호출
copy[key] = deepCopy(obj[key]);
});
return copy;
}
let member = {id: 1, name: 'Tom', membership: {level: 'premium'}};
let copiedObj = deepCopy(member);
member.name = 'Mike';
member.membership.level = 'basic';
console.log('member: ', member);
// {id: 1, name: 'Mike', membership: {level: 'basic'}};
console.log('copiedObj: ', copiedObj);
// {id: 1, name: 'Tom', membership: {level: 'premium'}};