얕은 복사, 깊은 복사

지은·2023년 2월 13일
3

JavaScript

목록 보기
37/42

중첩 구조를 가진 객체를 복사할 때,
(e.g. 배열 안에 배열, 배열 안에 객체, 객체 안에 배열, 객체 안에 객체)

  • 얕은 복사는 한 단계까지만 복사하고,
    깊은 단계, 즉 객체에 중첩되어있는 객체는 주소(참조값)를 복사한다.
  • 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사한다. 즉, 모든 단계를 복사한다.


💿 얕은 복사

const obj = { inObj: { x: 1 } };
const shallowCopied = { ...obj }; // { inObj: { x: 1 } }

console.log(obj === shallowCopied);             // false
console.log(obj.inObj === shallowCopied.inObj); // true


💽 깊은 복사

import _ from 'lodash'; // lodash의 cloneDeep 이용

const obj = { inObj: { x: 1 } };
const deepCopied = _.cloneDeep(obj); // { inObj: { x: 1 } }

console.log(obj === deepCopied);            // false
console.log(obj.inObj === deepCopied.inObj) // false


얕은 복사 vs 깊은 복사

얕은 복사와 깊은 복사로 생성된 객체는 원본과 다른 객체이다.
즉, 원본 객체와 복사본 객체은 다른 참조값을 가지고 있는 별개의 객체이다.

  • obj === shallowCopied ➡️ false
  • obj === deepCopied ➡️ false

하지만 이때,
얕은 복사는 객체에 중첩되어있는 객체의 경우, 참조값을 복사하고,
깊은 복사는 객체에 중첩되어있는 객체까지 모두 복사하여 원시값처럼 완전한 복사본을 만든다.

  • obj.inObj === shallowCopied.inObj ➡️ true
  • obj.inObj === deepCopied.inObj ➡️ false

💿 얕은 복사하는 법

전개 연산자 (Spread Operator) ...

const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const shallowCopied = { ...obj };

shallowCopied.inStr = 'DEF'; // 원시자료형 - 복사본이므로 원본 객체에 영향 X
shallowCopied.inObj.x = 2;   // 참조자료형 - 같은 주소를 참조하므로 원본 객체에 영향 O

console.log(obj);           // { inStr: 'ABC' , inObj: { x: 2 } }
console.log(shallowCopied); // { inStr: 'DEF' , inObj: { x: 2 } }

Object.assign()

const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const shallowCopied = Object.assign({}, obj);
// 위와 동일

slice()

const arr = [1, 2, 3, ['a', 'b', 'c'] ];
const shallowCopied = arr.slice();

shallowCopied.push(4);      // 원본 배열에 영향 X
shallowCopied[3].push('d'); // 원본 배열에 영향 O

console.log(arr);           // [1, 2, 3, ['a', 'b', 'c', 'd']]
console.log(shallowCopied); // [1, 2, 3, ['a', 'b', 'c', 'd'], 4]

Array.from()

const arr = [1, 2, 3, ['a', 'b', 'c'] ];
const shallowCopied = Array.from(arr);
// 위와 동일

➡️ 만약 중첩된 객체/배열을 복사하고 값을 변경할 경우, 예기치 못하게 원본 값을 변경시킬 수 있으므로, 얕은 복사는 중첩이 없고 값으로 원시자료형만 가지고 있을 때 사용하는 게 좋다.


💽 깊은 복사하는 법

JSON.parse/stringify

복제하고자 하는 객체를 직렬화하고 다시 JavaScript 객체로 만들면, 깊은 복사를 할 수 있다. 하지만 직렬화할 수 없는 속성은 복제할 수 없다는 제약이 있다.

const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const deepCopied = JSON.parse(JSON.stringify(obj));

deepCopied.inStr = 'DEF'
deepCopied.inObj.x = 2;

console.log(obj);        // { inStr: 'ABC' , inObj: { x: 1 } }
console.log(deepCopied); // { inStr: 'DEF' , inObj: { x: 2 } }

Lodash의 cloneDeep()

Lodash는 여러 유틸리티 기능을 제공하는 라이브러리로, Lodash의 cloneDeep() 함수를 사용하면 깊은 복사를 할 수 있다.

  • Lodash의 clone() 메소드는 얕은 복사를 할 수 있다.
import { clone, cloneDeep } from "lodash";

const arr = [1, 2, 3, ['a', 'b', 'c']];
const shallowCopied = clone(arr);
const deepCopied = cloneDeep(arr);

console.log(arr[3] === shallowCopied[3]);  // true
console.log(arr[3] === deepCopied[3]);     // false

직접 함수 만들기

재귀와 반복문을 이용해 직접 깊은 복사를 수행하는 함수를 만들 수 있다.

const deepCopy = (input) => {
  const output = Array.isArray(input) ? [] : {};
  let key;
  let value;
  
  if (typeof input !== 'object' || input === null) {
    return input;
  }
  
  for (key in input) {
    value = input[key];
    output[key] = deepCopy(value);
  }
    
  return output;
};

rfdc의 clone

rfdc(Really Fast Deep Clone) 라이브러리의 clone() 함수를 이용해 깊은 복사를 할 수 있다.
lodash보다 약 400% 빠른 깊은 복사를 수행한다.

const clone = require('rfdc')();

const arr = [1, 2, 3, ['a', 'b', 'c']];
const deepCopied = clone(arr);

복사 알고리즘의 성능 비교

  1. =로 재할당
  2. slice()
  3. rfdc의 clone() / 반복문 또는 재귀를 이용한 직접 만든 함수
  4. JSON.parse()
  5. lodash의 _.cloneDeep()
  • 우선 얕은 복사가 깊은 복사보다 빠르므로, 원시자료형으로만 이루어진 배열/객체라면 얕은 복사를 사용한다.
  • 깊은 복사가 필요한 경우에는 rfdc 라이브러리를 사용하는 것이 가장 빠르다.

이 글은 아래 링크를 참고하여 작성한 글입니다.
A Deep Dive into Shallow Copy and Deep Copy in JavaScript

profile
개발 공부 기록 블로그

6개의 댓글

comment-user-thumbnail
2023년 2월 13일

얕은복사 깊은복사를 얕게만 알고 있었는데 이렇게 자세히 설명해주시다니.. 정리도 깔끔하네요 가끔 헷깔리면 이글보면 될꺼같아요 !!

답글 달기
comment-user-thumbnail
2023년 2월 14일

깊은 복사에 저런 라이브러리가 있다니..! 좋은 정보 잘 배우고 갑니다!

답글 달기
comment-user-thumbnail
2023년 2월 15일

복사 방법이 이렇게 다양한 줄 몰랐네요 ! 덕분에 잘 배워갑니다 ~~!

답글 달기
comment-user-thumbnail
2023년 2월 16일

얕은복사 깊은복사 때문에 머리아팠던 기억이... 다시 공부하고 갑니다 !!

답글 달기
comment-user-thumbnail
2023년 2월 18일

얕은 복사 깊은 복사... 정리가 너무 깔끔해서 이해가 잘돼요 ㅠㅠ 안그래도 이 부분 때문에 고생했던 기억이 있었는데 완벽한데요! 잘 배우고 갑니당

답글 달기
comment-user-thumbnail
2023년 2월 19일

lodash 까진 알았는데 rfdc는 몰랐네요 이거 완전 꿀팁인데요? 깊은 복사 쓸 일이 있으면 저 라이브러리 가져와서 써보겠습니다 잘봤습니다!@

답글 달기