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

Yeojin Choi·2023년 4월 27일
0

Javascript

목록 보기
4/11

JavaScript에서 값은 원시값(primitive value)과 참조값(reference value) 두 가지 유형으로 나뉜다.

원시값은 Number, String, Boolean, Null, Undefined과 같은 데이터 타입이다.
이러한 원시값은 변수에 직접 할당된다.
변수에 할당된 원시값을 다른 변수에 할당하면, 두 변수는 각각의 복사본을 가지게 된다.
따라서, 한 변수의 값이 변경되어도 다른 변수의 값은 영향을 받지 않는다.

참조값은 객체, 배열, 함수와 같은 데이터 타입이다.
이러한 참조값은 변수에 할당될 때, 변수는 객체의 메모리 주소를 참조한다.
따라서, 변수가 객체를 참조하고 있는 경우, 다른 변수가 같은 객체를 참조하면 두 변수는 동일한 객체를 참조하게 된다. 한 변수에서 객체의 속성을 변경하면, 다른 변수에서도 변경된 속성을 볼 수 있다.

// 원시값
let a = 10;
let b = a;
a = 20;
console.log(b); // 10

// 참조값
let c = { x: 10 };
let d = c;
c.x = 20;
console.log(d.x); // 20

위의 코드에서 a와 b는 원시값을 참조하므로, a의 값이 변경되어도 b의 값은 변경되지 않는다.
하지만, c와 d는 참조값을 참조하므로, c의 속성 x의 값을 변경하면 d의 속성 x의 값도 변경된다.

이러한 특징 때문에 자바스크립트에서 복사에는 얕은 복사와 깊은 복사 두 가지 유형이 존재한다.

얕은 복사(Shallow copy)

얕은 복사는 원본 객체의 속성들을 참조하고 있는 새로운 객체를 생성하는 것이다.
즉 프로퍼티의 값이 객체배열일 경우, 원본 객체와 새로운 객체는 같은 객체나 배열을 참조하게 된다.
이러한 경우, 새로운 객체에서 객체나 배열을 변경하면 원본 객체의 값도 변경된다.

얕은 복사를 수행하는 방법으로는 Object.assign()이나 전개 연산자(...)를 사용할 수 있다.

// Object.assign()으로 얕은 복사
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);

console.log(obj2); // { a: 1, b: { c: 2 } }

obj2.a = 3;
console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 3, b: { c: 2 } }

obj2.b.c = 4;
console.log(obj1); // { a: 1, b: { c: 4 } }
console.log(obj2); // { a: 3, b: { c: 4 } }


// 전개 연산자로 얕은 복사
const obj3 = { d: 5, e: { f: 6 } };
const obj4 = { ...obj3 };

console.log(obj4); // { d: 5, e: { f: 6 } }

obj4.d = 7;
console.log(obj3); // { d: 5, e: { f: 6 } }
console.log(obj4); // { d: 7, e: { f: 6 } }

obj4.e.f = 8;
console.log(obj3); // { d: 5, e: { f: 8 } }
console.log(obj4); // { d: 7, e: { f: 8 } }

깊은 복사(Deep copy)

깊은 복사는 원본 객체의 프로퍼티들을 완전히 새로운 객체에 복사하는 것이다.
프로퍼티의 값이 객체나 배열일 경우, 새로운 객체에서 객체나 배열을 변경하더라도 원본 객체의 값은 변경되지 않는다.

재귀적인 방법 사용하는 방법

function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  const newObj = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    newObj[key] = deepClone(obj[key]);
  }

  return newObj;
}

객체의 모든 속성을 순회하면서 재귀적으로 객체를 복제한다.
이 방법은 순환 참조를 처리할 수 있는 장점이 있지만, 속도가 느릴 수 있다.

JSON 객체를 사용하는 방법

const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4]
  }
};

// JSON.stringify()로 객체를 문자열로 변환한 후
// JSON.parse()로 다시 객체로 변환하여 깊은 복사 수행
const obj2 = JSON.parse(JSON.stringify(obj1));

console.log(obj1); // { a: 1, b: { c: 2, d: [ 3, 4 ] } }
console.log(obj2); // { a: 1, b: { c: 2, d: [ 3, 4 ] } }

obj2.a = 5;
obj2.b.c = 6;
obj2.b.d[0] = 7;

console.log(obj1); // { a: 1, b: { c: 2, d: [ 3, 4 ] } }
console.log(obj2); // { a: 5, b: { c: 6, d: [ 7, 4 ] } }

JSON.stringify()를 사용하여 객체를 문자열로 변환한 후, JSON.parse()를 사용하여 다시 객체로 변환한다.
이 방법은 불완전한 객체나 함수, Symbol, undefined, Infinity, NaN과 같은 값은 복사하지 않는다는 단점이 있다.
또한, JSON.stringify()는 순환 참조를 다루지 않으므로 객체에 순환 참조가 있는 경우 예기치 않은 동작이 발생할 수 있다.

Lodash 라이브러리 사용

const _ = require('lodash');

const obj = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4]
  }
};

const clonedObj = _.cloneDeep(obj);

위의 예제에서는 _.cloneDeep() 함수를 사용하여 깊은 복사를 수행한다.
이 함수는 재귀적으로 객체를 탐색하여 객체의 모든 속성과 값을 모두 복사한다.
또한, 순환 참조를 다룰 수 있습니다.

하지만 Lodash 라이브러리를 추가해야 하므로, 라이브러리에 대한 의존성을 추가해야 한다.
Lodash 라이브러리의 크기가 크므로, 불필요한 기능들도 포함될 수 있다.
또한, 일부 브라우저에서는 Lodash 라이브러리를 사용하는 것이 성능에 부정적인 영향을 미칠 수 있다.

profile
프론트가 좋아요

0개의 댓글