Typescript의 얇은 복사 vs 깊은 복사

@SharkniA · January 09, 2025 · 10 min read

image1

서론

오랜만에 타입스크립트를 다루고 있습니다. 특히 코딩 테스트를 위해서 다루고 있는데, 오랜만에 타입스크립트의 배열을 다루다보니 얕은 복사와 깊은 복사에 관한 문제가 발생했습니다. 예전에 공부했던 내용이지만 정리한 적은 없는 것 같아 한 번 정리하고 넘어가려고 합니다.

TypeScript의 값 분류

TypeScript(JavaScript)는 "객체 기반 언어"(Object-Oriented Programming Language)입니다.

이는 객체를 활용하여 데이터를 구성하고 조작하는 데 최적화되어 있음을 의미합니다. 배열도 객체로 다루어지며, 대부분의 연산은 객체 참조를 기반으로 동작합니다. 대신 모든 값이 객체인 것은 아닙니다.

Primitive Value (기본형 값)

  • 불변형(Immutable) 값입니다.
  • 값을 직접 복사하며, 다른 변수에 영향을 미치지 않습니다.
  • 기본형 값에는 다음이 포함됩니다.

    • number, string, boolean, null, undefined, symbol, bigint
  • 메모리 저장 방식

    • Primitive 값은 스택(Stack)에 직접 저장됩니다.
    • 값 자체가 변수에 저장되며, 독립적으로 동작합니다.

Reference Value (참조형 값)

  • 객체처럼 동작하며, 값이 아닌 참조를 복사합니다.
  • 참조형 값에는 다음이 포함됩니다.

    • object, array, function, Map, Set, Date
  • 메모리 저장 방식

    • 참조형 값은 힙(Heap)에 저장되며, 변수에는 객체의 메모리 주소(참조)가 저장됩니다.
    • 따라서, 여러 변수가 동일한 객체를 참조할 수 있습니다.

Primitive vs Reference 차이

Primitive Value 예시 (Immutable)

let a = 42;
let b = a;
b = 100;
console.log(a); // 42 (원본 값은 변경되지 않음)
console.log(b); // 100

ab는 독립적인 값을 가지고 있으며, 하나를 변경해도 다른 값에 영향을 미치지 않습니다.

Reference Value 예시 (Mutable)

const arr1 = [1, 2, 3];
const arr2 = arr1; // 참조 복사
arr2[0] = 100;
console.log(arr1); // [100, 2, 3] (원본 배열도 변경됨)
console.log(arr2); // [100, 2, 3]

arr1arr2는 동일한 배열 객체를 참조합니다. 하나를 수정하면 다른 것도 영향을 받습니다.

얕은 복사와 깊은 복사

얕은 복사란?

  • 얕은 복사(Shallow Copy)는 객체의 1차원 수준만 복사하는 것을 의미합니다.
  • 배열이나 객체를 복사할 때, 최상위 수준의 값만 복사되며, 만약 내부 요소가참조형 값(Reference Value)이라면, 이 값들은 참조(주소)만 복사됩니다.
  • 따라서, 얕은 복사된 객체나 배열은 원본과 일부 데이터를 공유하게 되어, 한쪽을 수정하면 다른 쪽도 영향을 받을 수 있습니다.

깊은 복사란?

  • 깊은 복사(Deep Copy)는 객체의 모든 수준을 복사하여, 원본과 완전히 독립적인 객체를 생성합니다.
  • 중첩된 데이터(예: 객체 안의 객체, 배열 안의 객체)까지 새로운 메모리를 할당합니다.

얕은 복사의 동작

얕은 복사가 어떻게 작동하는지 예시를 통해 메모리 구조와 함께 자세히 알아보겠습니다.

예제 1: Primitive Value

const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 얕은 복사
arr2[0] = 100;
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [100, 2, 3]

위는 언뜻 보기에는 깊은 복사로 동작하는 것으로 보이지만, const arr2 = [...arr1];는 얕은 복사를 시행합니다. "깊은 복사처럼 작동한다"는 것은 단지 불변형 값 덕분에 두 배열이 독립적이기 때문입니다.

메모리 구조

  1. arr1이 생성되면, 배열 자체는 힙(Heap)에 저장되고, 각 요소(1, 2, 3)는 Primitive Value이므로 스택(Stack)에 저장됩니다.
  2. const arr2 = [...arr1]

    • 스프레드 연산자(...)는 arr1의 각 요소를 복사하여 새로운 배열 arr2를 만듭니다.
    • 배열 자체는 새로운 객체로 힙(Heap)에 저장되지만, 복사된 Primitive 값들은 각각 스택(Stack)에 저장됩니다.
  3. arr2[0] = 100

    • arr2의 첫 번째 값을 스택(Stack)에서 변경합니다.
    • arr1은 독립적인 배열이므로 영향을 받지 않습니다.

예제 2: Reference Value

const arr1 = [{ a: 1 }, { b: 2 }, { c: 3 }];
const arr2 = [...arr1]; // 얕은 복사

arr2[0].a = 100;
console.log(arr1); // [ { a: 100 }, { b: 2 }, { c: 3 } ]
console.log(arr2); // [ { a: 100 }, { b: 2 }, { c: 3 } ]

이 예제에서는 "얕은 복사"의 문제가 발생하며, 참조가 공유됩니다.

메모리 구조

  1. arr1이 생성되면, 배열 자체는 힙(Heap)에 저장되고, 배열의 각 요소({ a: 1 }, { b: 2 }, { c: 3 })도 힙(Heap)에 저장됩니다.

    • arr1 배열은 힙에 저장된 객체들의 참조(주소)를 가집니다.
  2. const arr2 = [...arr1]:

    • 스프레드 연산자(...)는 arr1의 각 요소의 참조(주소)를 복사하여 새로운 배열 arr2를 만듭니다.
    • 배열 arr2는 새로운 객체로 힙(Heap)에 저장되지만, 각 요소는 arr1과 동일한 객체를 참조합니다.
  3. arr2[0].a = 100:

    • arr2[0]arr1[0]과 동일한 객체를 참조하므로, 변경이 두 배열에 영향을 미칩니다.

결론

얕은 복사는 항상 1차원만 복사하는 것이 핵심입니다. 하지만, 배열의 요소가 불변형 값(Primitive Value)인지, 참조형 값(Reference Value)인지에 따라 결과가 달라질 뿐입니다.

얕은 복사는 항상 동일한 작업을 수행합니다.

  • 1차원 요소만 복사하고, 요소가 불변형이면 값 복사, 참조형이면 참조 복사.

결과 차이는 배열 요소의 유형(불변형 vs 참조형)에 따라 다릅니다.

  • 불변형 값 → 깊은 복사처럼 작동.
  • 참조형 값 → 얕은 복사로 인해 참조 문제 발생.

요약

  1. 얕은 복사의 본질

    • 배열이나 객체의 1차원 요소만 복사합니다.
    • 요소가 불변형 값이면 값을 복사하고, 참조형 값이면 참조를 복사합니다.
  2. 결과의 차이

    • 배열의 요소가 불변형 값이면, "깊은 복사처럼" 보일 뿐입니다.
    • 배열의 요소가 참조형 값이면, "얕은 복사처럼" 참조를 공유합니다.

얕은 복사와 깊은 복사를 언제 사용해야 할까?

1. 얕은 복사(Shallow Copy)

  • 언제 사용하나요?

    • 데이터가 불변형 값(Primitive Value)로만 이루어진 경우.
    • 객체나 배열을 복사할 때, 수정하지 않는 1차원 데이터만 필요할 때.
  • 장점:

    • 수행 속도가 빠르고, 메모리를 덜 사용.
    • 간단한 데이터 구조에서는 충분히 적합.

2. 깊은 복사(Deep Copy)

  • 언제 사용하나요?

    • 데이터에 참조형 값(Reference Value)이 포함된 경우.
    • 데이터가 중첩된 구조(객체 안의 객체, 배열 안의 배열)를 가지는 경우.
    • 복사본이 원본과 완전히 독립적이어야 할 때.
  • 장점:

    • 원본 데이터와 완전히 독립적이므로, 수정해도 영향을 주지 않음.

결론

  • 복사 작업에서 얕은 복사와 깊은 복사의 차이를 명확히 이해하고, 데이터 구조와 사용 목적에 따라 올바른 방법을 선택해야 합니다.
  • 단순히 "복사다!"라고 덤비면 참조 문제나 의도치 않은 결과가 발생할 가능성이 큽니다.
  • 문제가 복잡하거나 확신이 없을 때는 깊은 복사를 기본으로 고려하는 것이 안전합니다.
@SharkniA
만 4살 백엔드 개발자
© SharkniA, Built with Gatsby.