자바스크립트의 객체 참조할당과 얕은복사, 깊은복사


함께 보면 좋은글



이전에 저희는 참조형 데이터에 대해서 알아보았습니다.

사실 참조형 데이터는 공부하면 공부할수록 많은 공부가 필요한 부분입니다.

그중에서도 저희는 오늘 참조할당과 얕은복사, 깊은복사에 대해서 알아보도록 하겠습니다.

 

이 포스트는 이전 포스트인 자바스크립트의 참조형 데이터 타입의 기초적인 지식이 필요한 포스트이므로, 만약 안보고 오셨다면 보고 오시길 추천드립니다.

 

 

자바스크립트의 참조형 데이터 타입

함께 보면 좋은글 2020/10/06 - [프로그래밍 언어/Javascript] - 자바스크립트의 기본형 데이터 타입 2020/09/02 - [프로그래밍 언어/Javascript] - 자바스크립트의 메모리 관리 이전에는 기본형 데이터 타입에

noogoonaa.tistory.com

 


객체의 참조할당 with 그림

자바스크립트를 배우고 있는 여러분들에게 객체를 복사하라고 하면

어떤 방식으로 복사를 시도할 것 같나요?

 

아마 대부분이 이러한 방식으로 복사를 생각 하실겁니다.

 

// obj 객체 생성 및 초기화
const obj = {
    a: 1,
    b: "test"
};

// obj2의 obj를 복사
let obj2 = obj;

 

하지만 이런 방식은 자바스크립트에선 복사라고 하지 않습니다.

그냥 객체를 참조하는 주소값을 할당한다고 합니다.

 

잘 이해가 안가신다구요?

그림을 통해 한번 알아보도록 하겠습니다.

 

 

해당 그림은 obj가 참조하는 객체 주소값을 obj2에 할당하는 방식을 설명한 그림입니다.

 

  • 3006번 메모리에 obj2 객체의 메모리를 생성합니다.

  • 그 후, obj가 참조하는 객체 주소값을 obj2 객체의 값으로 할당합니다.

이렇게하면 여러분들은 아마도 이게 복사라고 생각하실 수 있습니다.

하지만 이러한 방식은 문제가 꽤 많습니다.

그 이유는 아래 소스코드와 같은 방식으로 사용했을 때 예기치 않은 오류를 유발할 가능성이 있기 때문입니다.

 

// obj 객체 생성 및 초기화
const obj = {
    a: 1,
    b: "test"
};

// obj2의 obj를 복사
let obj2 = obj;

// obj2의 a요소를 3으로 변경
obj2.a = 3;

console.log(obj.a, obj2.a) // 3 3

 

이상합니다.

저희는 obj2의 a요소 값을 변경했는데, 왜 obj도 변경 되어졌을까요?

그림을 한번 보도록 하겠습니다.

 

 

  • obj2.a 요소의 값을 3으로 변경해 주여야 하므로 데이터 영역에서 3의 데이터 값이 없으므로 7002번 주소에 3을 저장합니다.

  • 그 후 obj2의 a요소에 해당하는 1040주소의 값을 7002로 변경합니다.

이렇게 하면 obj2를 변경했지만 obj도 변경되는 이유를 설명할 수 있게 되어집니다.

즉 obj와 obj2는 5001 주소를 바라보고 있고, 5001은 1040부터 1041까지의 메모리를 바라보고 있기 때문에

obj2의 a요소인 1040메모리 값을 변경해 주어 obj도 같이 변경되어지기 때문입니다.

 

그렇다면 자바스크립트에서 객체를 어떻게 복사 해야할까요?

 

사실 자바스크립트에서 객체를 복사하는 방법은 다양합니다.

그 다양한 방식을 얕은복사와 깊은복사로 분류할 수 있는데,

먼저 얕은복사부터 알아보도록 하겠습니다.


객체의 얕은복사(shallow copy) with 그림

얕은복사란 객체를 복사할 때 객체 내부의 기본형 데이터는 복사가 가능하지만, 객체 내부의 또 다른 참조형 데이터는 복사가 아닌 할당을 해버리는 것을 말합니다.

 

잘 이해가 안되실 겁니다.

소스로 이해해 보도록 하죠

 

// 얕은복사를 진행하기 위한 함수
function shallowCopy(value) {
  const result = {};
 
  for(const key in value) {
    result[key] = value[key];
  }
 
  return result;
}

// obj 생성
let obj = {
    a: 1,
    b: [1, 2]
}

// obj2에 obj얕은 복사 실행
let obj2 = shallowCopy(obj);

// obj2 요사 값 변경
obj2.a = 3;
obj2.b[1] = 4;

// 결과
console.log(obj.a, obj2.a)	// 1 3
console.log(obj.b, obj2.b)	// [1, 4] [1, 4]

 

어떤가요? 이해가 되시나요?

obj를 obj2변수에 얕은복사를 진행했는데 a요소는 obj2 객체만 변경되었지만 b요소는 obj도 같이 변경되었습니다.

 

이것이 바로 얕은복사(shallow copy) 입니다.

이 코드의 핵심은 shallowCopy 함수입니다.

그러므로 shallowCopy 함수를 한번 찬찬히 보도록 할까요?

 

// 얕은복사를 진행하기 위한 함수
function shallowCopy(value) {
  // 빈 객체를 생성
  const result = {};
 
  // 매게변수로 넘어온 객체를 빈 객체에 저장
  for(const key in value) {
    result[key] = value[key];
  }
 
  // 매게변수로 넘어온 요소로 채워진
  // 빈 객체였던 객체를 반환
  return result;
}

 

이 함수는 빈 객체를 하나 생성하여,

반복문을 이용해 매게변수로 받은 객체를 빈객체에 저장하고

빈 객체였던 객체를 반환해주는 방식의 함수 입니다.

 

메모리 그림을 통해 알아보면 아래와 같습니다.

 

 

매게변수로 받아온 객체의 모습이 이렇다고 가정할 때 shallowCopy 함수를 사용하게 된다면,

아래와 같은 메모리 구조가 나오게 됩니다.

 

 

  • 1080 주소에 result 이름을 가진 메모리를 생성합니다.

  • 1080주소에 9001주소를 저장합니다.

  • 9001은 1068 ~ 1069 주소의 값을 가지고 있습니다.

  • 1068은 a 요소의 값이 7001 주소를 저장합니다.

  • 1069는 b 요소의 값이 5010 주소를 할당 받습니다. 이 메모리는 obj의 b요소인 1041 주소의 값과 동일해 집니다.

이러한 방식을 얕은복사라 합니다.

이러한 구조를 띄고 있기 때문에 이전 obj2.b.1 의 값을 변경했을 때 obj.b.1 또한 같이 변경되었던 것입니다.

 

사실 이러한 얕은복사는 이렇게 함수로 만들지 않고도 다양한 방식으로 사용할 수 있습니다.

 

  • Object.assign() 메소드를 이용한 얕은복사

const a = {a: 1, b: 2};
const b = Object.assign({}, a);
 
console.log(b);	// {a: 1, b: 2}

 

 

  • 전개(Spread) 연산자를 이용한 얕은복사

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

 


객체의 깊은복사(deep copy)

이제 깊은복사에 대해서 알아보겠습니다.

깊은복사란, 내부 참조형 데이터를 할당받지 않고 복사하는 방식을 말합니다.

 

소스를 한번 보겠습니다.

 

// 깊은복사를 진행하기 위한 함수
function deepCopy(value) {
  const result = {};
 
  for(const key in value) {
    if (typeof value[key] === 'object') {
      result[key] = deepCopy(value[key]);
    } else {
      result[key] = value[key];
    }
  }
 
  return result;
}

// obj 생성
let obj = {
    a: 1,
    b: { c: 1, d: 2}
}

// obj2에 obj얕은 복사 실행
let obj2 = deepCopy(obj);

// obj2 요사 값 변경
obj2.a = 3;
obj2.b.c = 4;

// 결과
console.log(obj.a, obj2.a)	// 1 3
console.log(obj.b, obj2.b)	// {c: 1, d: 2} {c: 4, d: 2}

 

여기서도 가장 중요한 함수는 deepCopy 함수입니다.

 

// 깊은복사를 진행하기 위한 함수
function deepCopy(value) {
	// 빈 객체 생성
	const result = {};
    
	// 매게변수 데이터를 복사
	for(const key in value) {
		if (typeof value[key] === 'object') {
			// 참조형 데이터일 경우 재귀함수를 통해 한번 더 복사
			result[key] = deepCopy(value[key]);
		} else {
			// 참조형이 아닐경우 데이터를 빈객체에 저장
			result[key] = value[key];
		}
	}
    
    // 할당된 객체 반환
    return result;
}

 

이 함수는 얕은복사 함수에 확장형입니다.

반복문을 돌릴 때, 해당 데이터가 참조형일 경우 재귀함수를 이용해 기본형 데이터가 나올 때 까지 반복하는 함수입니다.

이러한 방법을 이용해 깊은 복사를 진행하게 됩니다.

 

이 깊은복사 또한, 함수를 구현하지 않아도 사용이 가능합니다.

바로 JSON.stringfy() 메소드를 활용해 구현이 가능합니다.

 

  • JSON.stringify() 메소드를 활용한 참조형 데이터 깊은복사

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

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

copiedObj.b.c = 3

obj.b.c === copiedObj.b.c //false 

 


마무리

오늘은 참조형 데이터 타입 복사에 대해서 알아보았습니다.

만약 포스트에 문제가 있거나 틀린 부분, 잘 이해가 안 되는 부분이 있다면

댓글을 남겨주시기 바랍니다.

이 글을 공유하기

댓글