본문 바로가기

자바스크립트

얕은복사와 깊은복사, structuredClone()

보통 객체를 복사할때 내부의 모든 값 까지 복사될거라 생각하지만 실제로는 얕은 복사가 수행되어 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀌는 불상사가 발생할 수 있다.

이런 현상이 발생하지 않으려면 ? → 불변 객체로 복사하자

객체를 복사하는 방법에 대해 찾아보면서 얕은 복사와 깊은 복사를 좀 더 자세하게 정리하고자 한다.

얕은 복사(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

  1. Ramda: 함수형 프로그래밍을 위한 라이브러리로, 불변성을 강조하며 다양한 유틸리티 함수를 제공
  2. Clone-deep: JavaScript 네이티브 타입을 재귀적으로 복제하는 간단한 라이브러리
  3. Deep-clone: 객체, 배열, Date 객체 등을 재귀적으로 복제하는 라이브러리로, 순환 참조도 처리가 가능 

객체 복사의 효율적인 사용방법

프로젝트의 요구사항과 복잡성을 고려하여 적절한 방법을 선택하자.

  1. 간단한 객체 : 스프레드 연산자나 Object.assign()
  2. 복잡한 중첩 객체 : Lodash의 cloneDeep() 또는 structuredClone()
  3. 성능 중요 시 : 직접 최적화된 깊은 복사 함수 구현
  4. 상태 관리 필요 시 : 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