[JavaScript] 얕은 복사와 깊은 복사

Seungmin Lee·2022년 7월 20일
0

JavaScript

목록 보기
8/14
post-thumbnail

우선 얕은 복사와 깊은 복사에 대해 알기 전에 원시 자료형과 참조 자료형에 대한 이해가 필요하다. 원시 자료형은 하나의 데이터를 담으면서 변수가 곧 데이터를 저장하는 메모리이고 string, number, boolean 등이 해당한다. 참조 자료형은 여러개의 데이터를 담으면서 변수에는 데이터의 보관함(heap)의 주소를 저장한다. 배열, 객체, 함수가 대표적인 참조 자료형이다.


얕은 복사(shallow copy)

얕은 복사는 참조(주소)값을 복사하고 배열, 객체와 같은 참조 자료형의 복사에서 볼 수 있다.

const obj = { a : 1 }
cont newObj = obj; // obj의 참조값을 할당

newObj.a = 2;

console.log(obj) // { a : 2}
console.log(obj === newObj) // true

newObj객체에 obj 객체를 할당할 경우, 데이터의 값 자체가 아닌 데이터의 참조값(주소)를 할당하게 되고 이를 참조 할당이라고 한다. 이 경우 원본(obj)과 복사본(newObj)가 서로 heap의 주소를 공유하게 되어 newObj.a 값을 변경하면 obj.a값 또한 변경된다.

깊은 복사(deep copy)

깊은 복사는 데이터의 값 자체를 복사하고 참조(주소)값은 복사하지 않는다. 원시 자료형의 복사가 깊은 복사에 해당한다.

const a = 1;
const b = a;
b = 2;

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

변수b에 변수a를 할당 후 b의 값을 재할당 해주었지만 a의 값은 변하지 않는다.

객체의 깊은 복사

객체와 배열과 같은 참조 자료형의 경우에는 메서드를 이용해야 깊은 복사를 할 수 있다. 배열이라면 slice(), 객체 내에 객체를 포함하지 않는 객체의 경우 Object.assign()Spread syntax 등을 이용하면 깊은 복사를 할 수 있다.

Object.assign()

// 객체 내에 객체를 포함 하지 않음
const obj = { a : 1, b : 2 };
const copy = Object.assign({}, obj);

console.log(copy) // { a : 1, b : 2 }

copy.a = 2;
console.log(copy) // { a : 2, b : 2 }

console.log(obj) // { a : 1, b : 2 }

console.log(obj === copy) // false
// 객체 내에 객체를 포함
const obj = {
  a: 2, 
  b: { c: 3 }
};
const copy = Object.assign({}, obj);

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

🚫 하지만 객체 내에 객체를 포함하는 객체는 이 메서드를 통해 깊은 복사를 할 수 없다. 객체 자체에는 깊은 복사가 이루어 지지만, 객체 내의 객체는 얕은 복사가 이루어지기 때문에 완전한 깊은 복사가 아니다. Spread syntax 역시 마찬 가지이다.

JSON.stringify() / JSON.parse()

JSON 객체 메서드를 이용하면 완전한 깊은 복사를 할 수 있다.

JSON.stringify() 메서드는 인수로 객체를 받고 객체를 문자열로 치환한다.
JSON.parse() 메서드는 문자열을 인수로 받고 문자열을 객체로 치환한다.

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

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

newObj.b.c = 3;

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

하지만 이 방법 또한 문제가 있다. 객체의 값 중 함수를 포함하고 있을 경우 이 메서드를 사용하고 나면 undefined로 처리된다는 점이다.

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

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

console.log(newObj.func); // undefined

cloneDeep()

사실 lodash 모듈cloneDeep() 메서드를 이용하면 간단하게 깊은 복사를 할 수 있다. 이 메서드를 사용하기 위한 방법은 찾아보면 간단히 알 수 있다.

const lodash = require("lodash");

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

const newObj = lodash.cloneDeep(obj);

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

이 외에도 재귀함수를 사용하는 방법 등이 있지만 cloneDeep() 메서드를 이용하면 간단히 구현할 수 있고, 실제로 흔히 사용하는 방법이라고 한다.

profile
<Profile name="seungmin" role="frontendDeveloper" />

0개의 댓글