[JAVASCRIPT] 얕은 복사 & 깊은 복사

박창조·2024년 4월 15일
0

javascript

목록 보기
9/11
post-thumbnail

자바스크립트를 공부하다보면 듣게 되는 개념 중 한가지가 바로 "얕은 복사" "깊은 복사"이다. 이것이 등장한 배경을 알아보고, 어떤 내용을 담고 있는지 알아보자.

자바스크립트에서의 데이터 타입

변수를 선언하고, 할당할 때 컴퓨터는 메모리에 비어있는 공간을 확보하고, 저장합니다. 자바스크립트에서 메모리에 데이터를 저장하는 값을 크게 “원시(기본) 타입(primitive type)”의 값과 “참조 타입(reference type)”의 값으로 나눌 수 있습니다.

  • 원시(기본) 타입 : 변경 불가능한 값(immutable value)
  • 참조 타입 : 변경 가능한 값(mutable value)

원시(기본) 타입 타입인 숫자, 문자열, boolean, null, undefined, Symbol은 모두 변화 될 수 없는 "불변값"입니다.

var A = 'abc';

var B = A;

위의 예시 코드에서 문자열을 담은 변수 A를 변수 B에 담았습니다. 이때, B = 'def'코드를 작성한다면 변수 A는 변하지 않고, 각각 개별로 저장되게 됩니다.

그 이유는 Javacript의 모든 데이터 값은 "불변값" 이기 때문에 메모리 상에서 변수에 값을 할당할 때, 즉시 값을 변경할 수 없습니다.
그래서 곧 바로 저장하는 것이 아니라, 다른 공간에 따로 값을 저장하고, 해당 공간의 메모리 주소를 변수에 저장합니다. 그래서 한 번 만든 값은 즉시 바꿀 수 없고, 변경은 메모리를 새로 만들고 그 주솟값을 다시 저장하는 과정을 통해서 이뤄지게 됩니다.

이처럼 문자열이던 숫자 값이던 한번 만들어진 값은 원시(기본) 타입의 값은 다른 값으로 변경할 수 없습니다.

반대로 원시(기본) 타입을 제외한 나머지 값인 참조 타입의 값은 모두 다른 값으로 변경 할 수 있습니다. 그 이유는 값 자체를 저장하는 것이 아니라, 데이터 영역을 값을 저장하는 "메모리 주소 값"을 저장하기 때문입니다. 그래서 얼마든지 다른 주소값으로 변경할 수 있습니다.

// {...}안에 내용은 데이터 영역
// {...} 내용이 저장 되어 있는 메모리의 주소값을 obj 변수에 저장
var obj = {
  a: 1, 
  b: "abc",
}

하지만, 참조 타입의 데이터가 "가변값"이라고 설명할 때의 "가변"은 참조형 데이터 자체를 변경하는 경우가 이니라, 그 내부의 데이터를 변경할때 성립합니다.
(새로운 메모리를 사용했기 때문 -> 새로운 데이터 사용)

➕➕➕ 맨 위의 그림에서 알 수 있 듯, 자바스크립트에서 원시(기본) 타입의 데이터를 제외한 모든 값은 참조 타입의 데이터 이면서 "객체"입니다.


불변객체(immutable object)

"생성 후 그 내부 상태를 바꿀 수 없는 객체"

Immutability(변경불가성)은 "함수형 프로그래밍" 패러다임을 따라가는 ReactVue.js 등 과같은 라이브러리나 프레임워크에서 사용되는 매우 중요한 기초가 되는 개념 중 하나입니다.

객체는 참조 타입의 데이터로 "가변값"입니다.

"가변"한다는 것은 저장되고 있는 참조값(메모리의 주소 값)이 변경 되었다는 것이고, 주소 값이 변경되었다는 것은 객체의 내부 데이터가 변경되어, 새로운 메모리를 할당 받아, 다른 데이터들이 저장 되었다는 것입니다.

즉, 다시 말해

불변 객체(immutable object)는 "내부의 데이터"를 변경할 수 없는 객체를 의미합니다.

이것은 디자인 패턴에서 사용되는 하나의 개념으로서, 불변성을 확보할 필요가 있는 경우(React의 상태관리, 사이드 이펙트(옆구리) 등)에 따로 불변 객체로 취급하여 사용합니다.

불변 객체(immutable object)의 불변성을 유지하기 위해, 객체를 새로 생성하고, 복사하는 과정에서 나온 개념이 "얕은 복사", "깊은 복사" 입니다.


얕은 복사 (Shallow Copy)

“객체를 복사할 때 원본 값과 복사된 값이 같은 메모리 주소를 가리키는 것

얕은 복사(Shallow Copy)은 말 그대로 얕은 부분, 바로 아래 단계의 값만을 복사하는 것을 말합니다.

객체는 생성이 되면 객체 내부에 포함하고 있는 데이터를 저장하는 공간(0x001)을 메모리에서 확보하는데, 이때 객체를 선언할 때 생성한 변수(식별자)에는 이 공간의 주솟 값(0x001)을 저장하게 됩니다.

이때 얕은 복사가 된다는 것은 이때 이 주솟 값(0x001)을 복사하는 것을 의미합니다.

//Shallow copy 예제

let original = { name: 'Jinny' }
let copy = original; // 참조 값을 복사

copy.name = 'chagjo'; // 원복 객체가 직접 변경됨

console.log(original) // 출력 : {name: 'chagjo'}
console.log(copy) // 출력 : {name: 'chagjo'}
console.log(copy === original) // 출력 : true

위의 예제 코드를 보면 original 변수copy 변수는 동일한 값을 출력하고, 비교 연산자를 통해 확인했을 때 두 변수가 동일합니다.

original 변수 에는 { name: 'Jinny' } 객체의 내용을 저장하고 있는 메모리의 주소값이 저장이 되고, copy변수original 변수를 할당할 때, original 변수에 저장 되어있는 { name: 'Jinny' } 객체의 내용이 저장되어 있는 메모리의 주소값이 동일하게 저장되는 것입니다.

따라서 위에서 볼 수 있는 결과 처럼, 복사된 객체를 변경하게 되면 원본 객체에도 영향을 주는 것을 볼 수 있습니다.

또한 이후에 다룰 깊은 복사(Deep Copy)를 통해 복사된 객체 안에 또 다른 객체가 있을 경우에도, 객체의 값 그대로가 아니라, 주소 값을 전달하는 방식이기 때문에 한 개의 객체라도 원본 객체를 참조한다면 "얕은 복사”가 되었다고 봅니다.


깊은 복사(Deep Copy)

“객체 복사하여 완전히 새로운 객체를 생성하는 것”

얕은 복사는 객체의 주솟값을 복사하는 것이라고 한다면, 깊은 복사는 원본 객체와 동일한 내용을 가지고 있지만, 완전히 독립적이면서 서로에게 어떤 영향도 주지 않는 객체를 만드는 것을 의미합니다. .”

즉 “객체의 내용을 저장하는 메모리의 공간”이 하나 더 생긴다는 것을 의미합니다.

보통 전달받은 객체에 변경을 하더라도, 원복 객체는 변하지 않아야 하는 경우가 종종 있습니다. 이럴때 깊은 복사(Deep Copy)를 통해 객체를 복사하여 사용하게 됩니다.

평범한 할당과 복사를 통해서는 기본적으로 얕은 복사를 진행하기 때문에 깊은 복사를 하기 위해서는 조금은 특별한 방법을 사용해야 합니다. 그 방법에는 크게 2가지가 있습니다.

첫번째로는 자바스크립트에서는 (1) Object.assign(생성할 객체, 복사할 객체)를 사용하여 복사하는 것, (2) 직접 새로운 객체를 생성하여 하나하나 복사를를 통해 깊은 복사를 수행할 수 있습니다.

//Object.assign(생성할 객체, 복사할 객체) 이용한 Deep copy 예제

const original = { 
	name: 'Jinny', 
	friends : {
		name: "changjo"
	}
};

const copy = Object.assign({}, original);
copy.friends.name = "heung min"

console.log(original === copy) // false
console.log(original.friends.name === copy.friends.name) // true

다음 예제는 Object.assign(생성할 객체, 복사할 객체) 를 통한 깊은 복사의 예시 입니다.

마지막의 출력 결과를 보면, original변수copy 변수를 비교한 결과는 같지 않는다는 것을 볼 수 있습니다.

하지만, 내부 안에 있는 또 다른 객체를 비교 했을 때는 같은 주소값을 저장하고 있다는 결과를 보여줍니다.

이렇게 결과가 나오는 이유는, Object.assign()을 통해 객체 자체의 내용을 그대로 복사가 되어 새로운 객체에 할당 해주지만, 객체 안에 또 다른 객체에 대해서는 얕은 복사를 통해 진행되는 것을 볼 수 있습니다.

//재귀 함수를 결함한 Deep copy 예제

const original = { 
	name: 'Jinny', 
	friends : {
		name: "changjo"
	}
};

//깊은 복사(Deep copy)를 진행하는 함수
function copyObj(original) {
		//
    let newObj = {};

    for (let key in original) { //object를 돌며 key를 가져옴
      if (typeof original[key] === 'object') {
				  // 객체안의 value가 객체일 때 재귀적으로 다시 한번 함수를 호출
          newObj[key] = copyObj(original[key]);
**      } else {
          newObj[key] = original[key];
      }
    }

    return newObj;
}

const copy = copyObj(origin);

console.log(original.friends.name === copy.friends.name) // false

그래서 위의 예제 코드와 같이 객체를 복사하는 함수(copyObj())를 작성 후 객체안에 객체를 발견하게 되면 함수를 재귀적으로 호출하게 함으로써 깊은 복사를 진행할 수 있습니다.

“배열도 자바스크립안에서 하나의 객체로써 동작하기 때문에, 배열에 대한 내용도 처리해줘야 합니다 :)”

이 외에 아주아주 간단한 또 한가지 방법은 JSON 객체인 JSON.parse()JSON.stringify()를 이용하는 것입니다.

아주 단순하게 JSON.stringify()를 이용하여 주어진 복사할 대상 객체를 문자열로 만들고, JSON.parse()을 이용하여 해당 문자열을 다시 객체 형태로 변환하여 저장해 주면 됩니다.

//JSON.stringify()과 JSON.parse()를 사용한 Deep Copy 예제

const original = { 
	name: 'Jinny', 
	friends : {
		name: "changjo"
	}
};

const copy = JSON.parse(JSON.stringify(original));
console.log(original.friends.name === copy.friends.name) // false

이렇게 문자열로 변환 했다가 다시 객체로 바꾸는 과정에서 완전히 새로운 객체를 생성하는 것과 동일한 효과를 볼 수 있습니다. 하지만 이 방법은 undefined, Symbol 등 일부 데이터 타입이나 순환 참조를 적절히 처리하지 못하는 단점이 있습니다.


참조

[JS] 참조 타입의 얕은 복사와 깊은 복사(Shallow Copy & Deep copy)

참조에 의한 객체 복사

[코어자바스크립트] 01. 불변 객체, 넌 누구냐?

JavaScript 객체와 불변성

책 <코어 자바스크립트>

profile
사랑을 꿈꾸는 냄새나는 개발자 입니다 :)

0개의 댓글