보통 객체를 복사할때 내부의 모든 값 까지 복사될거라 생각하지만 실제로는 얕은 복사가 수행되어 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀌는 불상사가 발생할 수 있다.
이런 현상이 발생하지 않으려면 ? → 불변 객체로 복사하자
객체를 복사하는 방법에 대해 찾아보면서 얕은 복사와 깊은 복사를 좀 더 자세하게 정리하고자 한다.
얕은 복사(Shallow Copy)
바로 아래 단계의 값만 복사하는 방법
(객체의 1차원 수준의 값)
얕은 복사 방법
① Object.assign() 메서드
const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);
shallowCopy.b.c = 3;
console.log(original.b.c); // 3 (중첩된 객체는 참조 공유)
② 스프레드 연산자
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 3;
console.log(original.b.c); // 3 (중첩된 객체는 참조 공유)
📌 얕은 복사의 한계
- 중첩된 객체나 배열은 참조를 공유한다.
- 중첩된 객체를 수정할 시, 원본 데이터도 변경된다.
깊은 복사(Deep Copy)
내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법
(객체의 모든 중첩된 객체까지 완전히 새로운 메모리 공간에 복사)
✅ 깊은 복사의 장점
- 완전히 독립적인 새로운 객체를 생성하므로 원본 데이터를 보존할 수 있다.
깊은 복사 방법
① JSON.parse() & JSON.stringify()
◦ 장점 : 간단하다.
◦ 단점 : 함수, undefined, 순환 참조 등은 처리가 불가하다.
* 순환 참조 : 객체가 자기 자신이나 다른 객체를 계속 참조하는 구조(깊은 복사 시 특별한 처리가 필요한 복잡한 메모리 구조)
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 3;
console.log(original.b.c); // 2 (완전히 독립적인 복사)
② 재귀 함수를 이용한 깊은 복사
◦ 장점 : 특정 요구사항에 맞춰 구현 가능
◦ 단점 : 복잡하고 각 중첩된 객체/배열마다 함수를 재귀적으로 호출하므로 성능 저하 가능성이 있다.
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 배열인 경우
if (Array.isArray(obj)) {
return obj.map(deepClone);
}
// 객체인 경우
const copied = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copied[key] = deepClone(obj[key]);
}
}
return copied;
}
③ structuredClone() 내장 함수 사용
- JavaScript의 객체 깊은 복사를 위한 내장 함수
- ECMAScript 2022(ES13)에 공식 도입
- 구조화된 복제 알고리즘을 사용해 복잡한 데이터 구조 효율적 복사
- 순환 참조 완벽 처리 가능
◦ 장점 : 네이티브 구현으로 별도의 라이브러리 불필요, 간단하고 직관적인 API
◦ 단점 : 함수, DOM 노드, Symbol 타입 복사 불가 → DataCloneError 에러 발생
const deepCopy = structuredClone(originalObject);
④ 외부 라이브러리 사용
1. Lodash 의 cloneDeep()
Lodash - 자바스크립트 유틸리티 라이브러리로 배열, 숫자, 객체, 문자열 등을 다루는 다양한 메서드 제공
◦ 장점 : 다양한 데이터 타입 지원, 높은 성능
◦ 단점 : 번들 크기가 큼, 전체 Lodash 라이브러리를 import 해야 함
const _ = require('lodash');
const deepCopy = _.cloneDeep(originalObject);
2. Immer 의 produce()
Immer - 불변 데이터 구조 작업에 특화된 자바스크립트 라이브러리로 상태 변경을 쉽게 관리할 수 있다.
"produce" 함수는 원본 상태와 업데이트 함수를 인자로 받아 새로운 불변 상태를 생성한다.
장점 : 최소한의 코드로 복잡한 상태 변경이 가능하고 React, Redux 와 호환이 좋다.
단점 : 깊은 복사 전용 라이브러리가 아니며 복잡한 상태관리에 최적화 되어있다.
const produce = require('immer').produce;
const deepCopy = produce(originalObject, draft => {
draft.details.age = 31;
});
3. Ramda, Clone-deep, Deep-clone
- Ramda: 함수형 프로그래밍을 위한 라이브러리로, 불변성을 강조하며 다양한 유틸리티 함수를 제공
- Clone-deep: JavaScript 네이티브 타입을 재귀적으로 복제하는 간단한 라이브러리
- Deep-clone: 객체, 배열, Date 객체 등을 재귀적으로 복제하는 라이브러리로, 순환 참조도 처리가 가능
객체 복사의 효율적인 사용방법
프로젝트의 요구사항과 복잡성을 고려하여 적절한 방법을 선택하자.
- 간단한 객체 : 스프레드 연산자나 Object.assign()
- 복잡한 중첩 객체 : Lodash의 cloneDeep() 또는 structuredClone()
- 성능 중요 시 : 직접 최적화된 깊은 복사 함수 구현
- 상태 관리 필요 시 : Immer 고려
'자바스크립트' 카테고리의 다른 글
콜백함수 (0) | 2024.12.14 |
---|---|
this (0) | 2024.12.13 |
실행컨텍스트(Execution Context) (0) | 2024.12.03 |
스택(Stack)과 힙(Heap) (0) | 2024.12.02 |
데이터 타입 (0) | 2024.11.26 |