JS/Javascript

[javascript] 객체 6 / 객체와 참조 복사 (원시값, 값 복사, 참조 복사, 얕은 복사, 깊은 복사)

ui-o 2025. 9. 2. 15:55
반응형

원시값(Primitive)과 객체(Object) 비교

구붐 원시값(Primitive)  객체(Object)
정의 자바스크립트에서 더 이상 쪼갤 수 없는 기본 데이터 타입 원시값이 아닌 모든 데이터 타입
종류 string, number, boolean, null, undefined, symbol, bigint Object, Array, Function, Date 등
메모리 저장 방식 스택(Stack) 영역에 직접 값 저장 힙(Heap) 영역에 값 저장,
스택에는 힙 주소(참조) 저장
변경 가능 여부 불변(Immutable) – 값 자체를 변경할 수 없음 가변(Mutable) – 객체 내부 속성 변경 가능
복사 방식 값 자체가 복사됨 (값 복사) 참조값이 복사됨 (참조 복사)

값 복사(Value Copy) 와 참조 복사 (Reference Copy)

 

값 복사(Value Copy) – 원시값

  • 원시값은 값 자체가 복사됨 
  • 값 자체를 복사 → 서로 독립
  • 즉, 한 변수의 값을 다른 변수에 할당해도 서로 영향을 주지 않음
  • 대상 : 원시값(숫자, 문자열, boolean 등)
let a = 10;
let b = a; // a의 값을 b에 복사
b = 20;

console.log(a); // 10
console.log(b); // 20

/*
- a와 b는 서로 독립적임.
- 원시값은 스택 메모리에 바로 저장되기 때문에 값 자체가 복사됨
*/

참조 복사(Reference Copy) – 객체

  • 객체는 힙 메모리에 저장되고, 변수에는 객체의 주소값(참조)가 저장됨
  • 변수에 객체의 주소를 복사 → 변경 시 원본 영향
  • 대상 : 객체, 배열, 함수
let obj1 = { name: "Alice" };
let obj2 = obj1; // obj1의 참조를 obj2에 복사

obj2.name = "Bob";

console.log(obj1.name); // "Bob"
console.log(obj2.name); // "Bob"

/*
- obj1과 obj2는 같은 객체를 가리키므로, 한 쪽에서 바꾸면 다른 쪽도 영향을 받음.
- 주소값이 복사되었지, 실제 값이 복사된 것은 아님.
*/

✓ 참조 = 지도 → 참조/주소


값 복사 vs 참조 복사

항목 값 복사(Value Copy) 참조 복사(Reference Copy)
대상 원시값 객체, 배열, 함수 등
메모리 값 자체를 복사 참조 주소를 복사
변경 시 영향 독립적, 서로 영향 없음 동일 객체를 참조하므로 서로 영향 있음
예시 let b = a (원시값) let b = obj (객체)

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

얕은 복사(Shallow Copy)

  • 최상위 속성만 새로 복사, 중첩 객체는 공유
  • 객체나 배열을 복사할 때 최상위 레벨만 새로운 객체/배열로 생성
  • 내부에 중첩된 객체나 배열은 원본과 같은 메모리 주소(참조)를 공유 - 중첩 객체까지는 새로 복사하지 않음
  • 따라서 중첩된 객체/배열을 변경하면 원본에도 영향이 있음
  • 대상 : 객체, 배열

✓  얕은 → 겉만 새로 만들고 깊이는 그대로

특징

항목 내용
복사 범위 최상위 객체/배열만 복사
내부 객체/배열 참조 공유
원본 영향 최상위 변경 → 영향 없음(독릭접), 중첩 변경 → 영향 있음(원본과 공유)
대표 메서드 concat(), slice(), map(), filter(), reduce(), Array.from(), [...spread]

 


얕은 복사에 해당하는 메서드/연산

 

  • 배열 관련: concat(), slice(), map(), filter(), reduce(), Array.from()
  • ES6 스프레드 연산자: [...arr], { ...obj }, Object.assign({}, obj)
let arr1 = [1, 2, { a: 10 }];
let arr2 = arr1.slice(); // 얕은 복사

arr2[0] = 100;        // 최상위 값 변경
console.log(arr1[0]); // 1 → 원본 영향 없음

arr2[2].a = 50;       // 중첩 객체 변경
console.log(arr1[2].a); // 50 → 원본도 변경됨

 

let obj1 = {
  name: "Alice",
  info: { age: 25, city: "Seoul" }
};

// 얕은 복사
let obj2 = { ...obj1 }; // 스프레드 연산자 사용
obj2.name = "Bob";      // 최상위 속성 변경
obj2.info.age = 30;     // 중첩 객체 속성 변경

console.log(obj1.name); // "Alice" → 최상위 속성은 독립적
console.log(obj1.info.age); // 30 → 중첩 객체는 참조 공유

/*
- 최상위 속성은 새로 생성
- 중첩 객체는 참조를 공유 → 변경하면 원본에도 영향
*/

1. Object.assign()을 활용한 복사

  • 대상 : 객체
  • 복사 범위 : 최상위 속성만 → 얕은 복사
  • 여러 객체를 병합할 때 유용
/* [문법] */
Object.assign(복사된 속성이 들어갈 객체, 복사한 원본 객체(들))

/* [문법] - 새 객체를 만들려면 빈 객체 {}를 대상객체로 사용 */
Object.assign({}, 복사한 원본 객체(들));
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = Object.assign({}, obj1); // obj1을 obj2로 얕은 복사

obj2.a = 10;           // 최상위 속성 변경
console.log(obj1.a);   // 1 → 원본 영향 없음

obj2.b.c = 20;         // 중첩 객체 속성 변경
console.log(obj1.b.c); // 20 → 원본도 영향
let objA = { x: 1 };
let objB = { y: 2 };
let merged = Object.assign({}, objA, objB); 
console.log(merged); // { x: 1, y: 2 }

2. 스프레드 연산자(...)를 활용한 복사

  • 대상 : 객체, 배열(배열은 [...arr])
  • 최상위 속성만 → 얕은 복사
  • 문법이 간단, 직관적
/* [문법] */
let 새객체 = { ...원본객체 };
let 새배열 = [...원본배열];
// 객체 복사 예
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = { ...obj1 }; // 얕은 복사

obj2.a = 10;
console.log(obj1.a);   // 1 → 독립적

obj2.b.c = 20;
console.log(obj1.b.c); // 20 → 중첩 객체는 공유
// 배열 복사 예
let arr1 = [1, 2, { x: 10 }];
let arr2 = [...arr1]; // 얕은 복사

arr2[0] = 100;
console.log(arr1[0]); // 1 → 독립적

arr2[2].x = 50;
console.log(arr1[2].x); // 50 → 내부 객체는 공유

깊은 복사(Deep Copy)

  • 모든 속성, 중첩 객체/배열까지 새로 복사완전 독립
  • 객체 내부의 모든 속성, 중첩 객체, 배열까지 재귀적으로 새 객체 생성
  • 원본과 완전히 독립
  • 대상 : 객체 배열

특징

 

  • 원본 객체와 완전히 분리
  • 중첩된 객체/배열 변경 시 원본에 영향 없음

 

let obj1 = {
  name: "Alice",
  info: { age: 25, city: "Seoul" }
};

// 깊은 복사
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name = "Bob";
obj2.info.age = 30;

console.log(obj1.name); // "Alice" → 원본 영향 없음
console.log(obj1.info.age); // 25 → 중첩 객체도 독립적

/* ※
- JSON.stringify 방식은 함수, undefined, Symbol 등을 처리하지 못함
- 실무에서는 Lodash 같은 라이브러리의 _.cloneDeep()을 많이 사용
*/

 


1. JSON 방식을 활용한 복사 : JSON.parse(JSON.stringify(obj))

기본 원리

    • JSON.stringify(obj) → 객체를 JSON 문자열로 변환
    • JSON.parse(...) → JSON 문자열을 다시 객체로 변환
    • 결과: 중첩 객체/배열까지 모두 새로 생성 → 완전한 깊은 복사
    • 모든 자료형을 안전하게 복사하고 싶으면 _.cloneDeep(obj) 홀용

장점

  • 모든 자료형(객체, 배열, Date, Map, Set 등)을 안전하게 깊은 복사 가능
  • 함수, Symbol 등 특수값도 안전하게 처리
  • JSON 방식보다 더 완벽한 깊은 복사

단점

 

  • 큰 객체일 경우 성능 부담

 

import _ from "lodash";

let obj1 = {
  a: 1,
  b: { c: 2 },
  d: [3, 4],
  e: new Date()
};

let obj2 = _.cloneDeep(obj1);

obj2.b.c = 100;
obj2.d[0] = 50;

console.log(obj1.b.c); // 2 → 원본 영향 없음
console.log(obj1.d[0]); // 3 → 원본 영향 없음
console.log(obj1.e === obj2.e); // false → Date 객체도 새로 생성

얕은 복사 vs 깊은 복사

구분 얕은 복사 깊은 복사
복사 범위 최상위 속성만 중첩 객체/배열까지 재귀적으로 모두
내부 객체/배열 참조 공유 새 객체/배열 생성
원본 영향 중첩 객체 변경 시 영향 있음 원본에 영향 없음
방법 스르페드 연산자, Object.assign() JSON.parse(JSON.stringify(obj)), _.cloneDeep()

반응형