프로필 로고

[JS 기초] 원시형 vs 참조형 타입, 얕은 복사와 깊은 복사

반응형

 

 

 

 

"참조형 데이터는 값을 직접 가지지 않고, 해당 값이 저장된 메모리 주소를 가리킨다"

 

 

 

1. 원시형(Primitive) vs 참조형(Reference)

  • 원시형 데이터 (숫자, 문자열, 불리언, null, undefined, symbol, bigint)값 자체를 변수에 저장합니다.
  • 참조형 데이터 (객체, 배열, 함수)는 값이 저장된 메모리 주소를 변수에 저장합니다.

즉, 참조형 변수는 실제 값을 가지고 있는 게 아니라 그 값이 어디 있는지를 가리키는 포인터(주소)를 가지고 있는 것!

let a = { name: 'Sallamon' };
let b = a;
b.name = 'Lemon';

console.log(a.name); // ❓ 무엇이 출력될까요? Lemon 출력
  • a와 b는 서로 다른 객체를 갖고 있는 게 아니라, 동일한 객체를 가리키는 메모리 주소를 공유합니다.
  • b.name = 'Lemon'로 바꾸면 a.name도 같이 바뀌는 것처럼 보이는 이유
    사실 같은 객체의 같은 프로퍼티를 바꿨기 때문입니다.

 

 

 

 

2. 스택(Stack)과 힙(Heap)

 

구분 설명
스택(Stack) 변수 이름과 해당 주소 정보를 저장하는 공간
- 원시 값 및 참조
- 컴파일 타임의 크기를 알 수 있음
- 고정된 크기의 메모리 할당
힙(Heap) 실제 객체나 배열 같은 큰 데이터를 저장하는 공간
- 객체 및 함수
- 런타임의 크기를 알 수 있음
- 객체 당 제한 없음
let a = { x: 1 };
  • a는 스택에 저장되고,
  • { x: 1 } 객체는 에 저장됩니다.
  • 스택의 a는 힙의 주소를 가리킵니다.

 

 

 

 

3. 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

앞서 말씀드린 참조형, 스택과 힙과 관련하여 배열을 복사할 때 두 가지 방법이 있습니다.

 

 

(1) 얕은 복사(Shallow Copy)

표면(참조)만 복사한다는 뜻이에요. 
배열이나 객체 안에 또 다른 객체나 배열이 있으면, 그 안쪽은 그냥 주소(참조)만 복사해요.

 

< 배열 얕은 복사 >

let original = ['Apple', 'Banana', 'Cherry'];
let shallowCopy = original;

shallowCopy[1] = 'Blueberry';

console.log(original);      // ['Apple', 'Blueberry', 'Cherry']
console.log(shallowCopy);   // ['Apple', 'Blueberry', 'Cherry']
  • 둘 다 같은 배열을 가리키니까, 하나를 바꾸면 다른 것도 바뀌는 것처럼 보입니다.

 

<객체 얕은 복사>

let original = {
  name: 'Alice',
  details: {
    age: 25,
    city: 'New York'
  }
};

let shallowCopy = { ...original };

shallowCopy.details.age = 30;

console.log(original.details.age); // 30
  • details 안은 주소만 복사되었기 때문에, 
    shallowCopy.details를 수정하면 original.details도 같이 바뀝니다.

 

 

(2) 깊은 복사(Deep Copy)

겉만이 아니라 속까지 모두 복사하는 방식이에요. 
복사된 배열이나 객체는 완전히 별개의 값이 되어, 서로 영향을 주지 않아요.

 

< 방법 1 : JSON 방식 (간단하고 빠름) >

let original = ['Apple', 'Banana', 'Cherry'];
let deepCopy = JSON.parse(JSON.stringify(original));

deepCopy[1] = 'Blueberry';

console.log(original);    // ['Apple', 'Banana', 'Cherry']
console.log(deepCopy);    // ['Apple', 'Blueberry', 'Cherry']
  • 원본은 안 바뀜! 완전히 다른 배열이 만들어진 거예요. 
  • 단점: 함수, undefined, Symbol, 순환 참조가 있을 경우 복사 실패할 수 있습니다.

 

< 방법 2 : 직접 복사 함수 만들기 (재귀적으로 내부까지 복사)

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

  let result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    result[key] = deepCopy(obj[key]);
  }
  return result;
}

const original = {
  name: 'Sallamon',
  info: { age: 30, hobbies: ['code', 'music'] }
};

const copy = deepCopy(original);
copy.info.age = 99;

console.log(original.info.age); // 30 (안 바뀜)

 

 

(3) 왜 굳이 복사 방식을 나눠야 할까?

  • 참조형 데이터는 메모리 주소를 공유하기 때문에,
    • 얕은 복사는 수정이 원본까지 영향을 줘서 예기치 않은 버그가 날 수 있습니다.
    • 깊은 복사원본 보호, 독립적인 데이터 관리가 가능해집니다.

 

<중첩 구조에서의 문제 예시>

let original = {
  user: {
    profile: {
      name: 'Alice',
      age: 25
    }
  }
};

let shallowCopy = { ...original };
shallowCopy.user.profile.age = 30;

console.log(original.user.profile.age); // ❗ 30 (원본이 바뀜)
  • profile 객체까지는 주소만 복사되었기 때문에, 깊은 곳을 바꾸면 원본도 바뀜

 

 

 

 

결론

구분 얕은 복사 깊은 복사
복사 방식 겉만 복사, 안은 주소만 공유 안쪽 구조까지 모두 새로 만듦
원본에 영향을 주는가 영향 줌 영향 안 줌
복사 방법 =, ..., Object.assign() JSON.stringify/parse(), 재귀 함수, lodash.cloneDeep()
주의사항 중첩 구조에서 버그 유발 성능 주의 (복잡할수록 느림)

 

 

 

 

반응형