프론트엔드 짧은 간단 지식 정리 - 깊은 복사와 얕은 복사

이상범·2024년 9월 25일
0

1. 복사의 기본 개념 🤔

JavaScript에서 객체나 배열을 다룰 때, 복사는 매우 중요한 개념입니다. 복사 방식에 따라 예상치 못한 버그가 발생할 수 있기 때문입니다.

🎯 핵심 차이점

구분깊은 복사얕은 복사
복사 대상실제 값주소(참조) 값
메모리새로운 메모리 공간동일한 메모리 참조
독립성완전히 독립적내부 객체는 연결됨
수정 영향원본에 영향 없음내부 객체 수정 시 원본 영향

2. 깊은 복사 (Deep Copy) 🏊‍♂️

깊은 복사는 원본 객체의 모든 값을 새로운 메모리 공간에 복사하여 완전히 독립적인 객체를 만드는 방식입니다.

🔍 깊은 복사의 특징

  • 실제 값을 새로운 메모리 공간에 복사
  • 내부 객체나 배열도 재귀적으로 복사
  • ✅ 원본과 완전히 분리된 독립적인 객체 생성
  • ✅ 복사본 수정이 원본에 영향 없음

💡 깊은 복사 예시

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]

🛠️ 깊은 복사 구현 방법들

1️⃣ JSON 방법 (가장 간단)

const deepCopy = JSON.parse(JSON.stringify(original));

⚠️ 주의사항: 함수, undefined, Symbol, Date 객체 등은 복사되지 않습니다.

2️⃣ 재귀 함수로 직접 구현

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;
    }
}

3️⃣ Lodash 라이브러리 사용

import _ from 'lodash';
const deepCopy = _.cloneDeep(original);

3. 얕은 복사 (Shallow Copy) 🏄‍♂️

얕은 복사는 객체의 최상위 레벨만 복사하고, 내부의 참조형 데이터는 원본과 동일한 참조를 공유하는 방식입니다.

🔍 얕은 복사의 특징

  • ⚠️ 주소 값을 복사 (참조 복사)
  • ⚠️ 1depth만 새로운 객체로 복사
  • ⚠️ 내부 객체는 원본과 동일한 참조 유지
  • ⚠️ 내부 객체 수정 시 원본에도 영향

💡 얕은 복사 예시

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' (영향 받음!)

🛠️ 얕은 복사 구현 방법들

1️⃣ Spread Operator (가장 많이 사용)

// 객체
const shallowCopy = { ...original };

// 배열  
const shallowCopy = [...original];

2️⃣ Object.assign()

// 객체
const shallowCopy = Object.assign({}, original);

// 배열
const shallowCopy = Object.assign([], original);

3️⃣ Array 메서드들

// slice()
const shallowCopy = original.slice();

// concat()
const shallowCopy = [].concat(original);

4. 복사 방법별 비교 📊

🎯 상황별 적합한 복사 방법

// 테스트용 복잡한 객체
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 DepthMulti Depth함수Date성능추천도
{...obj}🔥🔥🔥⭐⭐⭐
Object.assign()🔥🔥🔥⭐⭐
JSON.parse()🔥⭐⭐
재귀함수🔥🔥⭐⭐⭐⭐
Lodash🔥🔥⭐⭐⭐⭐⭐

5. 실전 활용 팁 💡

⚠️ 자주 발생하는 실수들

// 🚨 실수 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]  // 내부 배열도 복사
}));

🎯 실전 상황별 가이드

📝 React State 업데이트

// ❌ 잘못된 방법
state.users[0].name = 'Updated Name';  // 직접 수정
setState(state);

// ✅ 올바른 방법 - 얕은 복사
setState({
    ...state,
    users: state.users.map(user => 
        user.id === targetId 
            ? { ...user, name: 'Updated Name' }
            : user
    )
});

🔄 API 데이터 가공

// 서버에서 받은 데이터를 가공할 때
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
        }
    };
};

6. 마무리 🎉

🌟 핵심 정리

구분언제 사용?주의사항추천 방법
얕은 복사• 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));
profile
프론트엔드 입문 개발자입니다.

0개의 댓글