도리쓰에러쓰

[JavaScript] 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) 본문

JavaScript/JS

[JavaScript] 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

강도리 2022. 8. 8. 15:25

1. 얕은 복사(Shallow Copy)

얕은 복사(Shallow Copy)는 참조값의 복사를 나타낸다.

const obj = { a: 1 };
const copyObj = obj;

copyObj.a = 2;

console.log(obj.a); // 2
console.log(obj === copyObj); // true

obj라는 객체를 copyObj라는 객체에 복사하여 copyObj.a값을 변경하였더니 기존의 obj.a값도 같이 변경되었다.

마찬가지로 두 객체를 비교해봐도 true가 나온다.

이렇게 자바스크립트의 참조 타입은 얕은 복사(Shallow Copy)가 된다고 볼 수 있으며,

이는 데이터가 그대로 생성되는 것이 아닌 해당 데이터의 참조 값을 전달하여 한 데이터를 공유하는 것이다.


2. 깊은 복사 (Deep Copy)

깊은 복사는 얕은 복사(Shallow Copy)처럼 참조값이 복사되는 것이 아니라, 값만 복사되는 것을 의미한다.

const a = 1;
const b = a;

b = 2;

console.log(a); // 1
console.log(b); // 2
console.log(a === b); // false

위 코드는 변수  a 에 1을 할당하고, 변수  b  a 의 값을 할당한 코드이다.

변수  b 의 값을 변경하여도 기존의  a 의 값은 변경되지 않는다.

두 값을 비교하여도 false가 출력되며 서로의 값은 단독으로 존재한다는 것을 알 수 있다.

위 예제처럼 자바스크립트의 원시 타입은 깊은 복사(Deep Copy)가 되며,

이는 독립적인 메모리에 값 자체를 할당하여 생성하는 것이라고 볼 수 있다.


💡 객체의 깊은 복사

객체를 그대로 복사하여 사용하면 기존 객체의 데이터가 변경될 수 있기 때문에

객체의 값을 복사하여 사용하고 싶다면 얕은 복사(Shallow Copy)가 아닌 깊은 복사(Deep Copy)를 사용하는 것이 좋다.

지금부터 객체를 깊이 복사하는 방법은 무엇이 있는지 알아보자.

 

1️⃣ Object.assign()

- 문법: Object.assign(생성할 객체, 복사할 객체);

const obj = { a: 1 };
const copyObj = Object.assign({}, obj);

copyObj.a = 2;

console.log(obj); // { a: 1 }
console.log(obj === copyObj); // false

위 예제는 copyObj라는 객체를 Object.asign() 메소드를 사용하여 생성한 코드이다.

copyObj.a 값을 변경하여도 기존의 obj.a의 값이 바뀌지 않았다.

서로의 객체를 비교해도 false가 뜨며 서로 참조값이 다른 것을 알 수 있다.

 

📍 Object.assign()은 2차원 객체를 복사하였을 때 깊은 복사가 이루어지지 않는다 !

const obj = {
  a: 1,
  b: { c: 2 },
}

const copyObj = Object.assign({}, obj);

copyObj.b.c = 3;

console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj.b.c === copyObj.b.c); // true

위 예제는 obj라는 2차원 객체를 copyObj에 복사하고 copyObj.b.c 값을 변경한 예제이다.

기존 obj 객체를 출력해보면 Object.assign() 메서드를 사용하여 객체를 복사하였음에도 불구하고 값이 변경됐다.

이는 복사된 하위 객체 { c: 2 }도 결국 객체이기 때문에 얕은 복사(Shallow Copy)가 된 것이다.

그래서 Object.assign() 메서드는 1차원 객체만 깊은 복사(Deep Copy)해야하며, 전개 연산자(Spread Operator)도 마찬가지이다.

 

2️⃣ 전개 연산자(Spread Operator)

const obj = {
  a: 1,
  b: { c: 2 },
};

const copyObj = {...obj};

copyObj.b.c = 3;

console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj.b.c === copyObj.b.c); // true

전개 연산자(Spread Operator)도 Object.assign()과 마찬가지로 2차원 객체는 얕은 복사(Shallow Copy)가 되는 것을 확인할 수 있다.

 

3️⃣ JSON의 stringify()와 parse()

- stringify() 문법: JSON.stringify() 메소드는 객체를 인수로 받으며 받은 객체는 문자열로 치환

- parse() 문법: JSON.parse() 메소드는 문자열을 인수로 받으며 받은 문자열은 객체로 치환

const obj = {
  a: 1,
  b: { c: 2 },
};

const copyObj = JSON.parse(JSON.stringify(obj));

copyObj.b.c = 3;

console.log(obj); // { a: 1, b: { c: 2 } }
console.log(obj.b.c === copyObj.b.c); // false

위 예제는 obj 객체를 JSON.stringify() 메소드를 이용하여 문자열로 변환한 뒤

다시 JSON.parse() 메소드를 통해 객체로 변환한 예제이다.

 

2차원 객체도 깊은 복사(Deep Copy)가 되긴 하지만 2가지 문제가 있다.

1) 다른 방법에 비해 성능이 느리다.

2) JSON.stringify() 메소드는 함수를 만났을 때 undefined로 처리된다.

 

아래 예제 코드를 확인해보자.

const obj = {
  a: 1,
  b: { c: 2 },
  func: function() {
    return this.a;
  }
};

const copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj.func); // undefined

복사된 copyObj는 func가 없고 undefined로 출력되고 있다.

 

4️⃣ 커스텀 재귀 함수

객체의 깊은 복사(Deep Copy)를 위해 직접 커스텀 재귀 함수를 구현하는 방법도 있다.

const deepCopy = (obj) => {
  if (obj === null || typeof obj !== 'object') return obj;
  
  let copy = {};
  for (let key in obj) {
    copy[key] = deepCopy(obj[key]);
  }
  return copy;
};

const obj = {
  a: 1,
  b: { c: 2 },
  func: function () {
    return this.a;
  }
};

const copyObj = deepCopy(obj);

copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false

deepCopy() 함수로 obj 객체를 매개변수로 보낸 다음 인수값이 객체가 아닌 경우엔 그냥 반환하며,

객체인 경우 객체의 값 만큼 루프를 돌고 재귀를 호출하여 복사된 값을 반환한다.

복사된 copyObj 객체를 확인해보면 2차원 객체의 값도 깊은 복사(Deep Copy)가 되었으며, 객체의 함수도 제대로 출력되는 것을 확인할 수 있다.

 

5️⃣ lodash 라이브러리의 cloneDeep()

lodash 라이브러리의 cloneDeep() 메소드를 이용해 객체의 깊은 복사(Deep Copy)가 가능하다.

해당 라이브러리를 설치했다는 가정 하에 아래 코드를 확인해보자.

const obj = {
  a: 1,
  b: { c: 2 },
  func: function () {
    return this.a;
  }
};

const copyObj = lodash.cloneDeep(obj);

copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false

lodash 라이브러리의 cloneDeep() 메소드를 이용하면 깊은 복사를 간편하게 구현할 수 있다.

 

Comments