JavaScript에서 객체나 배열을 다룰 때, 복사는 매우 중요한 개념입니다. 복사 방식에 따라 예상치 못한 버그가 발생할 수 있기 때문입니다.
구분 | 깊은 복사 | 얕은 복사 |
---|---|---|
복사 대상 | 실제 값 | 주소(참조) 값 |
메모리 | 새로운 메모리 공간 | 동일한 메모리 참조 |
독립성 | 완전히 독립적 | 내부 객체는 연결됨 |
수정 영향 | 원본에 영향 없음 | 내부 객체 수정 시 원본 영향 |
깊은 복사는 원본 객체의 모든 값을 새로운 메모리 공간에 복사하여 완전히 독립적인 객체를 만드는 방식입니다.
let originalObject = {
a: 1,
b: {
c: 2,
d: [3, 4, 5]
}
};
// 깊은 복사: JSON 방법 사용
let deepCopy = JSON.parse(JSON.stringify(originalObject));
// 독립성 확인
console.log(originalObject === deepCopy); // false ❌
console.log(originalObject.b === deepCopy.b); // false ❌
console.log(originalObject.b.d === deepCopy.b.d); // false ❌
// 복사본 수정해보기
deepCopy.b.c = 999;
deepCopy.b.d.push(6);
console.log(originalObject.b.c); // 2 (변화 없음 ✅)
console.log(originalObject.b.d); // [3, 4, 5] (변화 없음 ✅)
console.log(deepCopy.b.c); // 999
console.log(deepCopy.b.d); // [3, 4, 5, 6]
const deepCopy = JSON.parse(JSON.stringify(original));
⚠️ 주의사항: 함수, undefined, Symbol, Date 객체 등은 복사되지 않습니다.
function deepCopyFunction(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(item => deepCopyFunction(item));
}
if (typeof obj === "object") {
const copy = {};
Object.keys(obj).forEach(key => {
copy[key] = deepCopyFunction(obj[key]);
});
return copy;
}
}
import _ from 'lodash';
const deepCopy = _.cloneDeep(original);
얕은 복사는 객체의 최상위 레벨만 복사하고, 내부의 참조형 데이터는 원본과 동일한 참조를 공유하는 방식입니다.
let originalArray = [1, 2, [3, 4], { name: 'John' }];
// 얕은 복사: spread operator 사용
let shallowCopy = [...originalArray];
// 최상위 레벨은 독립적
console.log(originalArray === shallowCopy); // false ❌
// 하지만 내부 객체들은 같은 참조를 가짐
console.log(originalArray[2] === shallowCopy[2]); // true ✅ (같은 배열 참조)
console.log(originalArray[3] === shallowCopy[3]); // true ✅ (같은 객체 참조)
// 내부 객체 수정해보기
shallowCopy[2].push(5); // 내부 배열에 5 추가
shallowCopy[3].name = 'Jane'; // 내부 객체의 name 변경
// 원본에도 영향이 미침! 🚨
console.log(originalArray[2]); // [3, 4, 5] (영향 받음!)
console.log(originalArray[3].name); // 'Jane' (영향 받음!)
// 객체
const shallowCopy = { ...original };
// 배열
const shallowCopy = [...original];
// 객체
const shallowCopy = Object.assign({}, original);
// 배열
const shallowCopy = Object.assign([], original);
// slice()
const shallowCopy = original.slice();
// concat()
const shallowCopy = [].concat(original);
// 테스트용 복잡한 객체
const testObject = {
number: 42,
string: "hello",
array: [1, 2, { nested: "value" }],
object: {
deep: {
property: "test"
}
},
func: function() { return "I'm a function"; },
date: new Date(),
undefined: undefined,
nullValue: null
};
방법 | 1 Depth | Multi Depth | 함수 | Date | 성능 | 추천도 |
---|---|---|---|---|---|---|
{...obj} | ✅ | ❌ | ✅ | ❌ | 🔥🔥🔥 | ⭐⭐⭐ |
Object.assign() | ✅ | ❌ | ✅ | ❌ | 🔥🔥🔥 | ⭐⭐ |
JSON.parse() | ✅ | ✅ | ❌ | ❌ | 🔥 | ⭐⭐ |
재귀함수 | ✅ | ✅ | ✅ | ✅ | 🔥🔥 | ⭐⭐⭐⭐ |
Lodash | ✅ | ✅ | ✅ | ✅ | 🔥🔥 | ⭐⭐⭐⭐⭐ |
// 🚨 실수 1: 얕은 복사인데 깊은 복사로 착각
const users = [
{ id: 1, name: 'Alice', hobbies: ['reading'] },
{ id: 2, name: 'Bob', hobbies: ['gaming'] }
];
const usersCopy = [...users]; // 얕은 복사!
usersCopy[0].hobbies.push('writing');
console.log(users[0].hobbies); // ['reading', 'writing'] - 원본도 변경됨! 🚨
// ✅ 올바른 방법: 깊은 복사
const usersDeepCopy = JSON.parse(JSON.stringify(users));
// 또는
const usersDeepCopy = users.map(user => ({
...user,
hobbies: [...user.hobbies] // 내부 배열도 복사
}));
// ❌ 잘못된 방법
state.users[0].name = 'Updated Name'; // 직접 수정
setState(state);
// ✅ 올바른 방법 - 얕은 복사
setState({
...state,
users: state.users.map(user =>
user.id === targetId
? { ...user, name: 'Updated Name' }
: user
)
});
// 서버에서 받은 데이터를 가공할 때
const processApiData = (apiResponse) => {
// 원본 데이터 보호를 위한 깊은 복사
const processedData = JSON.parse(JSON.stringify(apiResponse));
// 안전하게 데이터 가공
processedData.items = processedData.items.map(item => ({
...item,
processedAt: new Date().toISOString()
}));
return processedData;
};
// 큰 객체의 일부만 수정할 때는 얕은 복사 + 부분 깊은 복사
const optimizedUpdate = (largeObject, pathToUpdate, newValue) => {
return {
...largeObject, // 얕은 복사로 성능 향상
[pathToUpdate]: {
...largeObject[pathToUpdate], // 필요한 부분만 깊은 복사
value: newValue
}
};
};
구분 | 언제 사용? | 주의사항 | 추천 방법 |
---|---|---|---|
얕은 복사 | • 1depth 객체 • 성능이 중요한 경우 • React state 업데이트 | 내부 객체 수정 주의 | {...obj} , [...arr] |
깊은 복사 | • 중첩된 객체/배열 • 원본 보호가 중요 • 복잡한 데이터 가공 | 성능과 함수 처리 | 재귀함수 , Lodash |
// ✅ 이럴 때는 얕은 복사
const userProfile = { name: 'John', age: 30 };
const updatedProfile = { ...userProfile, age: 31 };
// ✅ 이럴 때는 깊은 복사
const complexData = {
user: { profile: { settings: { theme: 'dark' } } }
};
const updatedData = JSON.parse(JSON.stringify(complexData));