JavaScript에서의 깊은 복사와 얕은 복사

YuJun Oh·2024년 8월 7일
0

객체 복사의 중요성

JavaScript에서 객체 복사는 단순해 보이지만 복잡한 주제입니다. 특히 데이터의 무결성 유지, 부작용 방지, 그리고 예측 가능한 코드 작성에 중요한 역할을 합니다. 이 글에서는 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)의 개념을 심도 있게 살펴보고, 각 방법의 장단점과 적절한 사용 시기를 알아보겠습니다.

얕은 복사 (Shallow Copy)

얕은 복사는 객체의 최상위 속성만을 새로운 객체에 복사합니다. 이는 중첩된 객체나 배열의 경우 원본 객체와 동일한 참조를 공유하게 됩니다.

예제 코드:

const original = {
    name: "Alice",
    age: 30,
    address: {
        city: "Seoul",
        country: "South Korea"
    },
    hobbies: ["reading", "coding"]
};

// 얕은 복사 수행
const shallowCopy = Object.assign({}, original);

// 또는 스프레드 연산자 사용
// const shallowCopy = { ...original };

shallowCopy.name = "Bob";
shallowCopy.address.city = "Busan";
shallowCopy.hobbies.push("gaming");

console.log(original.name);  // "Alice" (변경되지 않음)
console.log(original.address.city);  // "Busan" (변경됨!)
console.log(original.hobbies);  // ["reading", "coding", "gaming"] (변경됨!)

이 예제에서 name과 같은 원시 타입 속성은 독립적으로 복사되지만, addresshobbies와 같은 객체나 배열은 참조만 복사됩니다. 따라서 이들을 수정하면 원본 객체에도 영향을 미칩니다.

깊은 복사 (Deep Copy)

깊은 복사는 객체의 모든 수준에서 새로운 복사본을 생성합니다. 이는 중첩된 객체와 배열을 포함해 모든 데이터 구조를 완전히 독립적으로 복제합니다.

예제 코드:

const original = {
    name: "Alice",
    age: 30,
    address: {
        city: "Seoul",
        country: "South Korea"
    },
    hobbies: ["reading", "coding"]
};

// JSON을 이용한 깊은 복사
const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.name = "Charlie";
deepCopy.address.city = "Incheon";
deepCopy.hobbies.push("traveling");

console.log(original.name);  // "Alice" (변경되지 않음)
console.log(original.address.city);  // "Seoul" (변경되지 않음)
console.log(original.hobbies);  // ["reading", "coding"] (변경되지 않음)

이 방법은 간단하지만 함수, undefined, Symbol, 순환 참조 등을 처리하지 못하는 한계가 있습니다.

깊은 복사와 얕은 복사의 주요 차이점

  1. 데이터 독립성:

    • 얕은 복사: 중첩 객체는 원본과 참조를 공유
    • 깊은 복사: 모든 수준에서 완전히 독립적인 복사본 생성
  2. 메모리 사용:

    • 얕은 복사: 최소한의 추가 메모리만 사용
    • 깊은 복사: 모든 중첩 구조를 복제하므로 더 많은 메모리 사용
  3. 성능:

    • 얕은 복사: 빠름
    • 깊은 복사: 객체의 크기와 복잡도에 따라 상대적으로 느림
  4. 사용 사례:

    • 얕은 복사: 단순한 객체 구조나 성능이 중요한 경우
    • 깊은 복사: 데이터 무결성이 중요하거나 부작용을 피해야 하는 경우

복사 방법의 실제 구현

얕은 복사 구현 방법

  1. 스프레드 연산자 (...)
  2. Object.assign()
  3. Array.from() (배열의 경우)

깊은 복사 구현 방법

  1. JSON 변환 (JSON.parse(JSON.stringify()))
  2. 재귀 함수를 이용한 수동 구현
  3. 라이브러리 사용 (예: Lodash의 _.cloneDeep())

재귀적 깊은 복사 함수 예제:

function deepCopy(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    if (Array.isArray(obj)) {
        return obj.map(deepCopy);
    }

    const copiedObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copiedObj[key] = deepCopy(obj[key]);
        }
    }

    return copiedObj;
}

// 사용 예
const original = { a: 1, b: { c: 2 } };
const copy = deepCopy(original);

이 함수는 기본적인 깊은 복사를 수행하지만, 순환 참조나 특수한 객체 타입(예: Date, RegExp)에 대해서는 별도의 처리가 필요합니다.

언제 어떤 복사 방법을 사용해야 할까?

  1. 얕은 복사 사용:

    • 객체의 최상위 속성만 변경이 필요한 경우
    • 성능이 중요하고 중첩된 객체를 수정하지 않을 때
    • 임시적인 객체 복사가 필요한 경우
  2. 깊은 복사 사용:

    • 객체의 완전한 독립적인 복사본이 필요할 때
    • 상태 관리 시스템에서 불변성을 유지해야 할 때
    • 복잡한 데이터 구조를 안전하게 복제해야 할 때

성능과 메모리 고려사항

  • 얕은 복사는 일반적으로 빠르고 메모리 효율적이지만, 의도치 않은 부작용의 위험이 있습니다.
  • 깊은 복사는 더 안전하지만, 대규모 객체나 깊은 중첩 구조에서는 성능 저하가 발생할 수 있습니다.
  • 실제 애플리케이션에서는 성능 테스트를 통해 최적의 방법을 선택해야 합니다.

결론 및 모범 사례

  1. 목적에 맞는 복사 방법 선택: 데이터 구조와 사용 목적을 고려하여 적절한 복사 방법을 선택하세요.
  2. 불변성 유지: 특히 상태 관리에서는 불변성을 위해 깊은 복사를 고려하세요.
  3. 성능 최적화: 대규모 애플리케이션에서는 성능을 위해 얕은 복사와 깊은 복사를 적절히 조합하세요.
  4. 라이브러리 활용: 복잡한 경우 검증된 라이브러리를 사용하는 것이 안전할 수 있습니다.
  5. 테스트 강화: 복사 관련 로직에 대해 철저한 단위 테스트를 수행하세요.

JavaScript에서 객체 복사는 단순해 보이지만 많은 고려사항이 필요한 주제입니다. 이 가이드를 통해 얕은 복사와 깊은 복사의 개념을 명확히 이해하고, 프로젝트에 가장 적합한 방법을 선택할 수 있기를 바랍니다.

0개의 댓글

관련 채용 정보