티스토리 뷰

실무에서 복잡한 상태 관리 로직을 작성하다 보면 당혹스러운 순간을 마주하곤 합니다. 분명 A라는 객체를 복사해서 B를 만들고 B의 특정 값을 수정했는데, 원본인 A의 값까지 함께 바뀌어 버리는 현상입니다. 이는 자바스크립트의 메모리 할당 방식인 '참조(Reference)' 메커니즘을 정확히 제어하지 못했을 때 발생하는 전형적인 부작용입니다. 단순한 실수처럼 보이지만, 결제 시스템이나 데이터 시각화 도구에서 이런 논리 오류가 발생하면 서비스 신뢰도에 치명적인 타격을 줄 수 있습니다.

 

단순히 '얕은 복사는 위험하고 깊은 복사는 안전하다'는 식의 이분법적 접근은 위험합니다. 2026년 현재, 대규모 데이터셋을 다루는 웹 애플리케이션 환경에서는 성능(Performance)과 불변성(Immutability) 사이의 정교한 트레이드오프(Trade-off) 전략이 필요합니다. 개발자가 반드시 알아야 할 복사의 원리와 최신 구현 기법을 심층 분석합니다.

 

 

얕은 복사와 깊은 복사: 메모리 관점에서의 메커니즘 분석

자바스크립트 엔진이 데이터를 메모리에 저장하는 방식인 'Call Stack'과 'Heap'의 관계를 이해해야 합니다. 원시 타입(Number, String 등)과 달리 객체는 힙 메모리에 저장되며, 변수는 그 주소값만을 가집니다.

 

 

1. 얕은 복사 (Shallow Copy)의 한계

얕은 복사는 객체의 가장 바깥쪽 껍데기만 새로 생성합니다. 내부의 중첩된 객체들은 여전히 동일한 메모리 주소를 공유합니다. Object.assign()이나 전개 연산자(Spread Operator, ...)가 대표적입니다. 1단계 수준의 프로퍼티만 존재한다면 충분하지만, 2단계 이상의 중첩 구조에서는 '공유 참조' 문제로 인해 예기치 못한 상태 오염이 발생합니다.

 

 

2. 깊은 복사 (Deep Copy)의 필요성

깊은 복사는 재귀적으로 모든 하위 요소까지 새로운 메모리 공간에 복제합니다. 원본과 복사본은 완전히 독립된 개체가 됩니다. 하지만 모든 값을 새로 생성하고 메모리를 할당하는 과정에서 연산 비용이 기하급수적으로 증가하며, 특히 순환 참조(Circular Reference)가 있는 객체에서는 무한 루프에 빠질 위험이 있습니다.

 

 

성능 및 특성 비교 리포트 (2026 최신 기준)

데이터 크기(10,000개의 중첩 노드 기준)와 실행 환경에 따른 주요 복사 기법의 벤치마크 데이터와 특성을 정리한 표입니다.

 

구현 방식 평균 속도 (Ops/sec) 메모리 사용량 장점 단점 및 제약
Spread Operator 최상 (약 850,000) 매우 낮음 Native 지원, 가독성 우수 1단계 복사만 가능 (중첩 객체 불가)
structuredClone() 중상 (약 120,000) 중간 Native 깊은 복사, 순환 참조 지원 함수, DOM 노드 복사 불가
JSON.parse/stringify 하 (약 45,000) 높음 호환성 높음, 간단함 Date, RegExp, undefined 누락
Lodash _.cloneDeep 중 (약 70,000) 중간 안정성 검증됨, 다양한 타입 지원 외부 라이브러리 의존성 발생

 

 

실무 상황별 최적의 구현 솔루션

성능 손실을 최소화하면서 데이터 안정성을 확보하기 위해 다음의 4단계 프로세스를 권장합니다.

 

  1. 1단계: 단순 구조라면 전개 연산자 사용 뎁스가 1단계인 단순 설정값이나 UI 상태는 const newObj = { ...oldObj }; 방식을 사용하십시오. 가장 빠르고 표준적인 방법입니다.
  2. 2단계: 최신 브라우저 환경이라면 structuredClone 활용 2022년 이후 모든 모던 브라우저와 Node.js(v17+)에서 지원되는 structuredClone()은 별도의 라이브러리 없이 깊은 복사를 수행하는 가장 효율적인 Native API입니다.
  3. 3단계: 특수 타입(함수, 심볼) 포함 시 커스텀 재귀 함수 설계 복사 대상에 클래스 인스턴스나 함수가 포함되어 있다면, WeakMap을 활용하여 순환 참조를 방지하는 커스텀 로직을 구축해야 합니다.
  4. 4단계: 빈번한 업데이트 시 Immutable.js 또는 Immer 도입 React나 Vue 환경에서 1초에 수십 번씩 상태를 복사해야 한다면, 전체를 복사하는 대신 변경된 부분만 'Copy-on-write' 방식으로 처리하는 'Persistent Data Structure' 라이브러리를 사용하는 것이 성능상 유리합니다.

 

수석 연구원의 제언: 잘못된 상식 바로잡기

  • JSON 방식은 이제 지양해야 합니다: 과거에 흔히 쓰였던 JSON.parse(JSON.stringify(obj))는 객체 내의 메서드, undefined, Infinity 등을 모두 유실시키며 성능 또한 structuredClone 대비 약 2.5배 느립니다. 특수한 목적이 없다면 사용을 중단하십시오.
  • 과도한 깊은 복사는 안티 패턴입니다: 모든 데이터를 매번 깊은 복사하면 가비지 컬렉션(GC) 부하가 커져 애플리케이션이 간헐적으로 멈추는 'Jank' 현상이 발생할 수 있습니다. 변경이 필요한 부분만 선택적으로 복사하는 전략이 핵심입니다.
  • Ref 자료형의 특수성: Vue의 ref나 React의 useRef로 감싸진 객체를 복사할 때는 래퍼 객체의 특성을 고려하여 .value.current를 기준으로 복사를 수행해야 참조 무결성이 깨지지 않습니다.

결국 자바스크립트에서의 복사는 '편의성'과 '성능' 사이의 정밀한 조율 과정입니다. 데이터의 구조와 크기, 그리고 실행 환경의 제약 조건을 먼저 파악한 뒤 위 가이드라인에 맞춰 최적의 도구를 선택하시기 바랍니다.

단순히 지식을 전달하는 것을 넘어, 함께 성장하는 즐거움을 나누고 싶었습니다. 끝까지 봐주셔서 감사합니다.