[Codeit Sprint]Weekly Paper 3-2 JavaScript 얕은 복사와 깊은 복사

DreamPaste·2025년 4월 20일

Weekly Paper

목록 보기
6/8
post-thumbnail

1. 얕은 복사와 깊은 복사의 차이

얕은 복사는 객체의 주소(reference)만을 복사하는 방법입니다. 즉, 겉보기엔 새로운 객체를 만들지만 실제 내부 중첩된 객체나 배열은 원본과 동일한 메모리 위치를 가리키게 됩니다.
따라서 복사본이나 원본 중 한 쪽에서 중첩 객체를 변경하면 다른 한 쪽에도 그 변경이 반영됩니다.

반대로, 깊은 복사는 원본 객체의 모든 값을 재귀적으로 복사하여 완전히 독립적인 객체를 만드는 것입니다. 깊은 복사된 객체는 중첩 데이터까지도 별도 메모리에 복제되므로 원본과 복사본이 완전히 독립적인 상태가 됩니다. 즉, 한 쪽을 변경해도 다른 한 쪽에 아무 영향이 없습니다.

구분 얕은 복사 깊은 복사
복사 범위 최상위 속성만 복사 (중첩 객체/배열은 공유) 모든 중첩 구조까지 재귀적으로 복사
참조 관계 원본과 복사본이 중첩 데이터에 대해 같은 참조를 가짐 원본과 복사본의 모든 데이터가 완전히 독립
변경 영향 복사본에서 중첩 데이터 변경 시 원본에도 영향 복사본을 변경해도 원본에 영향 없음
성능 속도가 빠르고 메모리 사용 적음 (내용 전체를 복사하지 않음) 속도가 비교적 느리고 깊이 클수록 비용 증가
사용 사례 데이터 구조가 단순하거나, 중첩 데이터 변경이 필요 없을 때 복잡하게 중첩된 구조를 독립적으로 다뤄야 할 때

2. 왜 이렇게 복사하나요?

자바스크립트는 왜 얕은 복사와 깊은 복사를 나누어 사용할까?

JavaScript에서 얕은 복사와 깊은 복사가 나뉘어 존재하는 이유는 단순히 “복사의 깊이” 때문만이 아닙니다. 그 이면에는 성능, 메모리 효율, 언어의 철학, 그리고 개발자의 의도에 따른 선택이라는 깊은 배경이 있습니다.

📌 1. 메모리와 성능: 빠르고 가볍게 움직이기 위해

자바스크립트는 웹 브라우저에서 동작하는 언어로 시작됐고, 사용자 인터페이스(UI)를 실시간으로 업데이트하는 데 자주 사용됩니다. 이런 환경에서는 매우 빠른 응답성과 적은 메모리 사용이 중요합니다.

👉 그래서 기본적으로 참조형 데이터(객체, 배열 등)는 복사하지 않고 참조만 전달합니다.
즉, 얕은 복사가 기본 동작입니다.

전체를 매번 복사하면 느리고 메모리 낭비가 큽니다.

대신 “필요할 때만 깊은 복사를 개발자가 명시적으로 수행”하게 합니다.

이는 마치 종이문서를 복사할 때 전체를 인쇄하지 않고 표지에 메모만 적는 방식과 유사합니다. 복사 속도는 빠르지만 원본과 연결돼 있다는 점에서 주의가 필요한 선택입니다.

📌 2. 객체의 공유가 기본 설계다

JavaScript의 객체는 기본적으로 공유를 전제로 설계되어 있습니다.
즉, 여러 컴포넌트나 함수가 같은 객체를 가리키며 협업할 수 있도록 되어 있죠.

얕은 복사는 이러한 공유를 쉽게 가능하게 해 줍니다.

반대로 깊은 복사는 독립성과 안정성은 높지만, 비용이 비쌉니다.

이처럼 공유할지, 완전히 분리할지를 개발자가 선택할 수 있도록 설계된 것입니다.
이는 매우 유연한 접근 방식이며, JavaScript가 널리 사랑받는 이유 중 하나이기도 합니다.

📌 3. 복사는 비용이다 — 그래서 개발자가 결정한다

객체를 복사한다는 건, 단순한 “값의 복제”가 아닙니다.
중첩된 구조일수록 복사는 더 많은 재귀 호출, 더 많은 메모리, 더 느린 속도를 의미합니다.

🧠 JavaScript는 “복사의 깊이”가 많아질수록 성능 저하가 생길 수 있음을 알고 있고,
그 판단은 개발자의 몫으로 남겨두었습니다.

따라서 자바스크립트는 얕은 복사를 기본으로 제공하고,
정말 필요한 경우에만 개발자가 명확한 의도에 따라 깊은 복사를 수행하도록 설계된 것입니다.

3. 값 타입과 참조 타입에 따라 다릅니다.

JavaScript에서는 프리미티브 값(기본형)객체(참조형) 의 메모리 동작이 다릅니다. 숫자, 문자열, 불리언 같은 기본형 값은 값 자체를 복사하지만, 객체나 배열 같은 참조형은 값이 아닌 참조(주소) 를 다룹니다.
쉽게 말해, 객체를 변수에 할당하면 변수에는 객체의 내용이 직접 들어가는 것이 아니라 그 객체가 저장된 메모리 주소가 들어갑니다.

위 코드에서 ab는 숫자 값을 직접 갖고 있으므로 b를 변경해도 a는 변하지 않습니다. 그러나 obj1obj2의 경우 obj2 = obj1 할당 시 같은 객체를 가리키는 참조를 복사했기 때문에, obj2를 통해 수정한 내용이 obj1에도 그대로 반영됩니다. 이처럼 객체 복사는 기본적으로 얕은 복사로 동작하며, 두 변수 모두 동일한 객체(메모리 주소)를 참조하게 됩니다.

참조형에서 객체를 복사하는 두 가지 방법


위 코드에서 shallowCopy는 얕은 복사이므로 nested 객체를 원본과 공유합니다. 따라서 shallowCopy.nested.y 값을 바꾸면original.nested.y도 같이 변합니다.
반면 deepCopy 는 original과 완전히 독립적인 객체이므로, deepCopy.nested.y를 변경해도 original의 값은 바뀌지 않습니다. 마지막의 === 비교 결과에서도 얕은 복사본의 nested는 원본의 nested와 같지만, 깊은 복사본의 nested는 다르다는 것을 확인할 수 있습니다.

배열도 마찬가지입니다...

4. JavaScript에서 얕은 복사하는 방법

JavaScript에는 기본적으로 얕은 복사를 수행하는 여러 방법이 있습니다. 이러한 방법들은 객체의 프로퍼티나 배열 요소를 1단계(depth 1)만 복사하며, 그 이상의 중첩 구조는 복사되지 않습니다. 실제로 자바스크립트의 표준 내장 복사 기능들은 모두 깊은 복사가 아닌 얕은 복사로 동작합니다.

대표적인 얕은 복사 방법

  • 스프레드 문법 (...) – 객체나 배열을 펼쳐서 새로운 리터럴을 만들 때 사용합니다.
    ex: const newObj = { ...obj };, const newArr = [...arr];

  • Object.assign 메서드 – 객체를 복사할 때 사용하며, Object.assign({}, obj)는 빈 객체에 obj의 프로퍼티를 복사합니다.

  • 배열 메서드Array.prototype.slice()Array.prototype.concat()을 이용하여 새로운 배열을 만들면 기존 배열이 얕은 복사됩니다.

위 방법들은 모두 얕은 복사이므로, 중첩 객체나 배열은 참조만 복사된다는 점을 기억해야 합니다.

5. JavaScript에서 깊은 복사하는 방법

깊은 복사는 객체나 배열의 모든 계층의 데이터를 새로 복사하여 원본과 완전히 분리된 복사본을 만드는 것입니다. JavaScript에는 깊은 복사를 위한 몇 가지 접근법이 있으며, 각각 장단점이 있습니다.

대표적인 깊은 복사 방법

  • JSON 직렬화/파싱JSON.stringify()JSON.parse()를 조합하는 방법으로, 객체를 문자열로 변환했다 다시 객체로 파싱하여 깊은 복사를 수행합니다.

    ex: const deepCopy = JSON.parse(JSON.stringify(obj));
    이 방식은 간단하고 많이 사용되지만, 함수나 undefined 값, Date 객체, RegExp 등의 특수 값은 지원하지 않으며, 순환 참조(circular reference)가 있으면 오류가 발생합니다.

    예를 들어 객체가 서로를 참조하는 구조라면 JSON.stringify에서 에러가 납니다. 또한 Map, Set 같은 ES6 객체도 JSON으로 변환되지 않아 이 방법으로 복사할 수 없습니다.

  • structuredClone 함수 – 최신 JavaScript 환경(브라우저 및 Node.js 17+ 등)에서 사용 가능한 빌트인 깊은 복사 함수입니다. 사용 방법은 structuredClone(obj)처럼 간단하며, 순환 참조를 포함해 대부분의 내장 객체 타입(Date, RegExp 등 포함)을 잘 처리해줍니다.
    성능도 최적화되어 비교적 우수하며, JSON 방법의 한계를 많이 해결했습니다.
    다만, structuredClone도 함수나 DOM 노드, 그리고 프로토타입이 있는 객체(예: 클래스 인스턴스)는 복사하지 못하고 예외를 일으키거나 프로토타입 정보를 잃습니다.

    예를 들어 클래스 인스턴스를 structuredClone하면 결과가 순수한 객체가 되고, 객체에 함수 프로퍼티가 있으면 DataCloneError가 발생합니다.

  • 재귀적 복사 – 직접 함수를 작성해서 객체를 재귀적으로 순회하며 복사하는 방법입니다.

    위 deepClone 함수는 단순한 객체와 배열에 대해서는 제대로 동작하며, 재귀적으로 모든 레벨을 복사합니다. 다만 이 구현은 함수나 Date 같은 타입을 처리하지 못하고, 순환 참조가 있을 경우 무한 루프에 빠지는 단점이 있습니다. 이런 특별한 경우까지 처리하려면 구현을 더 복잡하게 만들어야 합니다. 실무에서는 직접 구현하기보다 검증된 라이브러리를 사용하는 편이 안전합니다.

  • 외부 라이브러리 활용 _.cloneDeep() 같은 검증된 유틸리티 함수를 쓰면 손쉽게 깊은 복사를 할 수 있습니다. LodashcloneDeep은 대부분의 상황에서 잘 동작하며, 순환 참조가 없다는 전제 하에 동작합니다.

✅ 마무리 요약

얕은 복사와 깊은 복사는 JavaScript 개발에서 자주 마주치는 개념이지만, 그 차이를 명확히 이해하지 못하면 예상치 못한 버그나 성능 이슈로 이어질 수 있습니다.

📃얕은 복사는 빠르고 가볍지만, 중첩된 데이터는 원본과 연결되어 있습니다.

📃깊은 복사는 완전히 독립적인 복제이지만, 비용이 크고 구현이 복잡할 수 있습니다.

자바스크립트는 기본적으로 얕은 복사를 전제로 설계되었으며, 개발자가 필요할 때만 명시적으로 깊은 복사를 수행하게끔 합니다.

profile
Pasting my dream in code

0개의 댓글