개발을 하다 보면, 자바스크립트에서 객체를 다룰 때 단순한 변수 복사와 다르게 객체의 복사는 사용하는 방식에 따라 예상과 실제 로직의 값이 다르게 나오기도 한다. 이는 객체가 참조 타입으로 지정되기 때문이며, 원본과 복사본이 어떻게 연결되는지에 따라 동작이 달라진다.
특히, 얕은 복사와 깊은 복사의 차이를 이해하지 못하면 원본 객체가 의도치 않게 변경되는 문제가 발생할 수 있다. 이를 올바르게 활용하려면 먼저 원시 값과 객체 값이 메모리에 할당되는 방식을 이해한 뒤, 두 복사 방식의 차이와 사용해야 하는 상황을 명확히 알아야 한다.
그렇다면 자바스크립트에서 데이터가 어떻게 저장되고, 복사될 때 어떤 방식으로 동작하는지 알아보자.
자바스크립트의 데이터 타입
자바스크립트의 모든 값은 원시 타입(Number, String, Boolean, undefined, null, Symbol, BigInt) 또는 객체 타입(Array, Function, RegExp, Set, Map, ...)으로 구분된다. 원시타입에 속하는 값은 변경 불가능한 원시 값이며, 객체 타입에 속하는 값은 가변적이며 참조에 의한 전달 방식을 사용한다.
자바스크립트의 데이터 타입과 관련한 내용은 아래의 링크(👇)에서 확인할 수 있어 본 글에서는 위와 같이 짧게 정리하고자 한다.
https://oliviakim.tistory.com/193
원시 값 할당 방식
할당 방식
변수에 원시 값을 할당하면, 값 자체가 메모리에 저장된다.
예를 들어 fruit 변수에 '사과'라는 문자열을 할당하면, 메모리에 fruit이라는 키와 '사과'라는 값이 저장된다.
이후, fruit 변수에 '오렌지'를 재할당하면 기존 값을 변경하는 것이 아니라, '오렌지'를 저장할 새로운 메모리 공간이 할당된다.
이처럼 원시 타입 변수는 새로운 값을 할당할 때마다 새로운 메모리 공간을 사용한다. 또한 원시 값을 다른 변수에 할당하면, 값 자체가 복산된다. 즉, 원시 값을 복사한 변수는 원본가 독립적인 값을 가지며, 변경해도 원본에 영향을 주지 않는다.
이처럼 원시 값은 값에 의한 복사 방식으로 메모리에 할당된다.
예시
- 변수 a에 숫자 5를 할당하면, 메모리에 a라는 변수 공간이 생성되고 값 5가 저장된다.
- 변수 b에 a를 할당하면, a의 값이 복사되어 b에 저장된다. (b는 a와 독립적인 값 5를 가짐)
- 따라서 console.log를 실행하면, '초기값 a는 5, b는 5'가 출력된다.
- 이후, a를 10으로 값을 변경하면, a 변수에 새로운 값 10이 저장된다.
- 하지만 b는 여전히 초기값 5를 유지한다. b는 a의 값을 복사받았을 뿐, a와 연결되어 있지 않기 때문이다.
- 따라서 두번째 console.log에서는 'a값을 변경한 후의 값: a는 10, b는 5'가 출력된다.
이처럼 원시 값은 값에 의한 복사 방식으로 전달되므로, 변수 간 독립적인 값을 가지게 된다.
객체 값 할당 방식
할당 방식
반면 객체는 참조(reference) 값으로 저장되며, 변수에는 객체의 메모리 주소가 저장된다. 새로운 변수를 기존 객체에 할당하면 객체의 참조 값(메모리 주소)만 복사되므로 같은 객체를 가리키게 된다. 따라서, 한 변수를 통해 객체의 속성을 변경하면, 같은 객체를 참조하는 다른 변수도 영향을 받는다.
예시(1)
people 변수에 { name: 'jonh', age: 20 } 이라는 객체를 할당하면, 메모리에 people이라는 키와 객체의 값을 바라보는 메모리의 주소값이 저장된다.
이후 people 변수의 name에 'anna'를 재할당하면, name 키의 value값이 anna라는 원시 값의 주소를 바라보도록 수정된다.
이 때 name이라는 key의 value값만 바뀌었을 뿐, name을 감싸고 있는 people 변수의 값의 주소가 변하진 않았다. 즉, 객체 타입의 변수에 할당된 메모리는 동일하게 유지되면서, 해당 주소에 저장된 실제 데이터는 변경될 수 있다는 것이다.
예시(2)
또 다른 예시를 살펴보자. obj_a를 obj_b에 할당하면, 새로운 객체가 생성되는 것이 아니라 동일한 객체를 참조한다. 따라서 obj_a.name을 변경하면, obj_b.name도 함께 변경된다.
얕은 복사
예시 코드 및 개념
얕은 복사는 객체의 참조 값(메모리 주소)만 복사하는 방식이므로, 원본과 복사본이 동일한 객체를 참조하게 된다. 따라서 복사한 객체의 중첩된 속성을 변경하면 원본에도 영향을 미친다.
구현 방법
스프레드 연산자 ({ ...obj })
Object.assign() 메서드
깊은 복사
개념
깊은 복사는 원본 객체뿐만 아니라 내부의 중첩된 객체까지 새로운 메모리 공간을 할당해 복사하는 방식이다. 이로 인해 복사된 객체는 원본과 완전히 독립적으로 동작하며, 어느 한쪽을 수정해도 서로 영향을 주지 않는다.
구현 방법
Lodash의 _.cloneDeep() 메서드
structuredClone() (최신 브라우저만 지원)
이 외에도 재귀적으로 모든 하위 객체를 복사하여 직접 깊은 복사를 구현하거나, JSON.parse(JSON.stringify(obj))를 활용하는 방법도 있다. 단, JSON.parse(JSON.stringify(obj)) 방식은 function, Symbol, undefined, Date, RegExp 같은 데이터 타입을 올바르게 복사하지 못하는 한계가 있어 주의해야 한다.
장점
깊은 복사는 원본과 완전히 분리된 새로운 객체가 생성되므로, 수정해도 원본에 영향을 미치지 않는다. 또한 내부 객체까지 새로운 메모리 공간에 저장되므로, 참조 공유 문제가 발생하지 않는다. 이로 인해 깊은 복사를 사용할 경우 원본 데이터의 무결성을 보호하고 예기치 않은 부작용을 방지할 수 있다.
그렇다면 깊은 복사가 항상 최선의 방법일까? 모든 상황이 그렇지는 않다. 얕은 복사는 메모리 사용량이 적고 속도가 빠르기 때문에, 꼭 필요한 경우가 아니라면 성능 측면에서 더 효율적일 수 있다. 하지만, 중첩 객체까지 복사해야 하거나 원본이 객체라면 원본 데이터의 무결성을 지켜주기 위해 깊은 복사를 사용하는 것이 좋다. 이처럼 상황에 따라 얕은 복사와 깊은 복사 중 어떤 방식이 내 상황에 더 적절한지 판단하고 사용하는 것 또한 중요하다.
'Front-End > JavaScript' 카테고리의 다른 글
[JavaScript] 자바스크립트의 동기, 비동기 프로그래밍의 개념 (2) | 2025.02.24 |
---|---|
[JavaScript] 객체 내부에서의 getter, setter (0) | 2024.08.19 |
[JavaScript] 객체의 속성에 접근하는 점 표기법(마침표 표기법)과 대괄호 표기법 (ft. 좋은 코드) (0) | 2024.08.15 |
[JavaScript] 특수 언어가 있는 문자열 배열을 정렬하는 방법, localeCompare() (3) | 2024.07.23 |
[JavaScript] JSON 알아보기 (0) | 2023.05.01 |