
오늘 들은 자바스크립트 3주차 강의에서는 '얕은 복사(Shallow Copy)'와 '깊은 복사(Deep Copy)'를 배웠다. 사실 나에게 '얕은/깊은 복사'는 유구한 전설이 있는데 ..
전 회사에서 신입 퍼블리셔로 근무하던 시기에 사수분께서 복사 떠본 적 있냐는 물음에 "복사요?.. 저기 복사기에서 하는 거 아니에요?" 라고 대답했다가 사수님이 할 말을 잃으신 사건이 있다. 결국 그 후에 나에게 시간 괜찮냐고 물으시며 회의실에서 1:1 과외를 해주셨는데 ..🤪 시니어 개발자분께 받는 몇 안되는 기회의 1:1 강의였지만.. 그 회의실 안에서까지도 결국 이해를 못한 채로 나왔었다 ··· 💦 쩝 ,.. 이번엔 진짜 제대로 이해하자는 마인드로 고다 고!
두 가지 개념을 간단히 설명하면 이러하다.
게임으로 예를 들어보자.
캐릭터의 inventory 안에 sword '검' 아이템이 있고, 그 검의 내구성은 100 이다. 또 다른 아이템으로는 potion이 있고 이 물약은 400개가 있다고 해보자. 객체로 표현하면 이렇게 되겠다.
let battleCharacter = {
name: 'Warrior',
health: 100,
inventory: [
{item : 'sword', durability: '100'},
{item : 'potion', quantity: '400'}
],
};
만약 전투를 하는 과정에서 캐릭터의 아이템 sword의 내구도가 떨어졌다고 가정해보자.
이 sword의 전체 내구도는 100이었는데, 전투에서 사용하면서 30까지 떨어졌다.
let battleCharacter = {
name: 'Warrior',
health: 100,
inventory: [
{ item: 'sword', durability: 100 },
{ item: 'potion', quantity: 400 }
],
};
// 얕은 복사
let copiedCharacter = { ...battleCharacter };
// 복사된 객체에서 인벤토리 아이템 수정
copiedCharacter.inventory[0].durability = 30;
console.log(battleCharacter.inventory[0].durability); // 30 (원본도 변경됨)
console.log(copiedCharacter.inventory[0].durability); // 30 (복사본도 동일한 참조)
얕은 복사로 캐릭터 정보를 복사해 검의 내구성을 떨어트렸다. 그러나 문제가 생긴다.
해당 sword 아이템의 원본 내구도도 함께 떨어지는 거다. 😱 이러면 대장간에 가서 검을 수리하더라도 전체 내구도를 30까지 밖에 올릴 수 없게 된다! 원본 내구도까지 변경되었기 때문이다.
이런 경우에는 원본과 복사본의 데이터 연결성을 끊어내기 위해 원본 상태를 변경하지 않고, 객체들이 완전히 복사되어 독립적으로 존재하는 깊은 복사가 필요하다.
그렇다면 더욱 고차원적인 복사의 개념이 '깊은 복사'인 것인데, 그럼 '얕은 복사'는 언제 쓰일까?
: 얕은 복사는 객체나 배열의 1차원적인 정보(1depth)만 복사하는 방식이다. 즉, 원본 객체나 배열의 최상위 속성들만 복사되고, 그 내부의 객체나 배열 같은 참조형 데이터는 복사되지 않은 채로 참조만 전달된다.
따라서 복사된 객체와 원본 객체가 내부 데이터를 공유하기 때문에, 하나의 객체에서 내부 데이터를 수정하면 서로 영향을 받는다.
// 원본 객체
let original = {
a: 1,
b: { x: 10, y: 20 }
};
// Object.assign()을 활용한 얕은 복사
let shallowCopy = Object.assign({}, original);
shallowCopy.a = 2; // 원본의 a와는 독립적
shallowCopy.b.x = 30; // 원본의 b.x와 공유됨
console.log(original); // { a: 1, b: { x: 30, y: 20 } }
console.log(shallowCopy); // { a: 2, b: { x: 30, y: 20 } }
Object.assign() 혹은 스프레드 연산자 ({...})로 한다.a, b 수준의 속성들은 복사되나 b 내부의 객체 및 배열은 데이터가 차지하는 메모리의 주소값만 복사된다.shallowCopy.b.x = 30으로 변경하게 되면, 같은 내부 객체 데이터 주소를 가진 원본과 복사본 데이터값이 함께 변경된다.
let player = {
name: 'Alice',
score: 50,
};
// 스프레드 연산자를 활용한 얕은 복사
let updatedPlayer = { ...player };
// 점수만 변경
updatedPlayer.score = 60;
console.log(player.score); // 50 (원본은 그대로)
console.log(updatedPlayer.score); // 60 (복사본만 변경됨)
score를 변경해도 중첩된 객체나 배열이 없기 때문에 원본 객체에 영향이 없다.: 객체나 배열 안에 있는 모든 속성, 즉 1차원적 속성 뿐만 아니라 그 안에 있는 모든 중첩된 객체까지 완전히 독립적으로 복사한다. 원본 객체와 복사된 객체가 완전히 분리되기 때문에 서로 영향을 미치지 않게 된다.
일반적인 구현 방식은 아래와 같다.
function deepCopy(target) {
// 기본 데이터 타입이나 null은 그대로 반환
if (target === null || typeof target !== "object") {
return target;
}
// 배열인 경우
if (Array.isArray(target)) {
const arrCopy = [];
for (let i = 0; i < target.length; i++) {
arrCopy[i] = deepCopy(target[i]); // 재귀적으로 복사
}
return arrCopy;
}
// 객체인 경우
const objCopy = {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
objCopy[key] = deepCopy(target[key]); // 재귀적으로 복사
}
}
return objCopy;
}
null, undefined, 숫자, 문자열, boolean 등 기본 데이터 타입은 변경 없이 그대로 반환한다.Array.isArray()로 판별하고, 배열 안의 항목들을 하나하나 재귀적으로 복사한다.for...in 반복문을 사용하여 속성들을 하나씩 복사한다. 속성값이 또 다른 객체나 배열일 경우, 그 값도 재귀적으로 복사된다. const original = {
a: 1,
b: {
x: 10,
y: [1, 2, 3]
},
c: [4, 5]
};
const copy = deepCopy(original);
copy.a = 2;
copy.b.x = 20;
copy.b.y[0] = 100;
console.log(original); // { a: 1, b: { x: 10, y: [1, 2, 3] }, c: [4, 5] }
console.log(copy); // { a: 2, b: { x: 20, y: [100, 2, 3] }, c: [4, 5] }
이 외에도 JSON.parse와 JSON.stringify를 이용한 방법도 있다.
function deepCopy(target) {
return JSON.parse(JSON.stringify(target));
}
undefined, 자기 자신을 참조하는 순환 참조 등을 복사할 수 없는 단점이 있다.다음 코드를 보고
shallowCopy와deepCopy가 어떻게 동작할지 예측해보세요. 각 동작 후에original객체와shallowCopy/deepCopy객체를 출력해주세요.// 1. 기본 객체 let original = { a: 1, b: { x: 10, y: 20 } }; // 2. 얕은 복사 let shallowCopy = Object.assign({}, original); // 3. 깊은 복사 함수 function deepCopy(target) { if (target === null || typeof target !== 'object') return target; if (Array.isArray(target)) { return target.map(item => deepCopy(item)); } let copy = {}; for (let key in target) { if (target.hasOwnProperty(key)) { copy[key] = deepCopy(target[key]); } } return copy; } // 4. 깊은 복사 let deepCopyObj = deepCopy(original); // 5. 수정 shallowCopy.b.x = 50; deepCopyObj.b.y = 100; // 6. 결과 확인 console.log("original:", original); console.log("shallowCopy:", shallowCopy); console.log("deepCopyObj:", deepCopyObj);위 코드의 콘솔은 어떻게 출력될까요?
정답은 아래에 ···!
console.log("original:", original); // a: 1, b: { x: 50, y: 20 }
console.log("shallowCopy:", shallowCopy); // a: 1, b: { x: 50, y: 20 }
console.log("deepCopyObj:", deepCopyObj); // a: 1, b: { x: 10, y: 100 }
야호! 얕은 복사와 깊은 복사에 대해 어느 정도 이해한 것 같다.
이제.. 응용이 문제랄까? 💫
#깊은복사 #얕은복사
어제 튜터님이 가르쳐주셨는데
비교적 최근에 생긴 문법중에 structuredClone( ) 이 있대요!
기존의 깊은 복사 방법들을 많이 개선한 문법이라 알아두시면 도움이 될지도
https://developer.mozilla.org/ko/docs/Web/API/Window/structuredClone