IT/JavsScript

얕은복사, 깊은복사 알아보기(feat.원시타입, 참조타입)

라임웨일 2021. 6. 4. 13:22
반응형

 

우리는 데이터의 값을 출력할 때 최초의 값에서 값이 추가되거나 삭제, 수정이 발생하는 경우가 생기게 됩니다.

이럴 경우 원본 데이터의 값에 바로 접근하여 사용하는 방법도 있지만 이와 같은 방법은 추후에 원본 데이터가 변경되었기 때문에 다양한 오류를 발생시킬 가능성이 있습니다.

 

그래서 효과적인 방법은 원본 데이터의 값을 복사하여 가공하거나 수정하는 방법을 사용하는 것이 좋습니다.

이런 경우에 사용하는 것이 바로 얕은 복사(shallow copy)와 깊은 복사(deep copy) 입니다. 

 

자바스크립트에서 원시 타입(primitive type)의 값은 새로운 메모리 공간에 독립적인 값을 저장하기 때문에 깊은 복사가 되고 참조 타입(reference type)값은 얕은 복사가 됩니다. 

 

잠깐.....

아직 얕은 복사와 깊은 복사에 대해서도 별다른 설명이 없었는데.. 

원시 타입이 어떻고 참조 타입이 어떻다고요?

 

괜찮습니다. 이제부터 하나씩 알아볼게요. 

우선 얕은복사와 깊은 복사를 알아보기 전에 우리는 원시 타입과 참조 타입에 대해서 먼저 알아볼게요.

자바스크립트에서 원시타입은 크게 6가지로 정의하고 있습니다.

 

원시 타입(primitive type)

  • 문자열 : String
  • 논리형 : Boolean
  • 숫자형 : Number
  • Null
  • undefined
  • Symbol

참조 타입(reference type)

  • 함수 : function
  • 배열 : Array
  • 객체 : Object

 

원시 타입과 참조 타입의 가장 큰 차이점은 원본이 바뀌면 참조 타입은 복사본도 같이 변경되지만, 원시 타입은 변경되지 않는다는 점이 큰 차이점입니다.

 

예시를 통해서 좀 더 정확히 살펴볼게요.

let a = '원본 데이터';
let b = a; 

a = '원본 데이터 수정';

console.log(a); // output: 원본 데이터 수정
console.log(b); // output: 원본 데이터

처음 변수 a에 "원본 데이터" 값을 넣고 변수 b에 a의 값을 할당하였습니다. 

이후 a에 새로운 값으로 재할당을 한 후 로그를 확인해보면 새롭게 할당한 변수의 값은 변경되었지만 처음 원본값을 할당받은 b의 값은 변경되지 않음을 확인할 수 있습니다. 

원시 타입은 복사 시 값 자체를 담은 독립적인 메모리를 생성하기 때문에 a가 재할당 되더라도 b에는 아무런 영향을 미치지 않습니다.

 

let obj1 = {name :'원본 데이터'}
let obj2 = obj1;

obj1.name = "원본 데이터 수정"

console.log(obj1)  //원본 데이터 수정
console.log(obj2)  //원본 데이터 수정

원시 타입과 달리 참조 타입인 오브젝트는 새로운 값으로 변수 값을 재할당 하자 복사된 변숫값도 같이 변경되는 것을 확인할 수 있습니다.  즉, 데이터가 그대로 하나 더 생성된 것이 아닌 해당 데이터의 메모리 주소를 전달하게 돼서, 결국 한 데이터를 공유하게 되는 걸 알 수 있습니다.

 

1. 얕은 복사(Shallow Copy)

  • 객체를 복사할 때, 해당 객체만 복사하여 새 객체를 생성합니다.
  • 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소를 참조합니다.
  • 따라서, 해당 메모리 주소의 값이 변경되면 원본 객체 및 복사 객체의 인스턴스 변수 값은 같이 변경됩니다.

2. 깊은 복사(Deep Copy)

  • 객체를 복사 할 때, 해당 객체와 인스턴스 변수까지 복사합니다.
  • 전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않습니다.
  • 객체가 참조 타입의 멤버를 포함할 경우 참조값의 복사가 아닌 참조된 객체 자체가 복사되는 것을 의미합니다. 원본의 참조는 더 이상 하지 않습니다.

 

위에서 원시 타입과 참조 타입, 얕은 복사와 깊은 복사의 큰 개념을 이해하고 아래 코드를 보면 조금 더 우리가 이해하려고 했던 개념을 좀 더 쉽게 이해할 수 있을 거예요.

// 변수 선언 (배열, 문자열, 오브젝트)
const a = []
const b = 'hello'
const c = {}

// 배열 선언 후 변수값 (a,b,c) 값을 할당
const arr = [a,b,c]

// 새로운 변수 arr1에 배열 값 arr을 할당
const arr1 = arr;

// 이러면 arr1에는 arr값과 동일한 값이 들어있을거에요. 
arr1[1] = 'hi'

console.log(arr1[1])  // hi
console.log(arr[1])   // hi

// 참조된 값을 변경했는데 원본데이터인 arr 값이 변경된 걸 확인할 수 있습니다. 


// 전개 연산자(얕은 복사)를 이용하여 얕은 복사로 arr2에 arr값을 복사하였습니다. 
const arr2 = [...arr]
console.log(arr2)     // [[], "hi", {}]

// 복사한 arr2의 문자열 값을 변경해 볼게요.
arr2[1] = 'welcome';

// 이번에는 원본값이 변경되지 않은 걸 확인할 수 있습니다. 
console.log(arr2[1])  // welcome
console.log(arr[1])   // hi

// 그럼 이번에는 배열에 새로운 값을 넣어 볼게요.
arr2[0].push(5)

// push로 배열값을 변경하였더니 원본값도 같이 변경 되었네요.
console.log(arr2[0])  // 5
console.log(arr[0])   // 5

// 그럼 이런 문제점을 막기 위해서 는 어떻게 하는게 좋을까요.
const arr3 = JSON.parse(JSON.stringify(arr))
console.log(arr3)     // [[5], "h1", {}]

// 깊은 복사로 값을 배열의 값을 복사한 객체값을 변경해 보겠습니다. 
arr3[0].push(2)

// 이번에는 push로 배열값을 변경해도 원본값도 변경 되지 않는걸 확인할 수 있습니다.
console.log(arr3[0])  // [[5, 2], "hi", {}]
console.log(arr[0])   // [[5], "hi", {}]

우리는 원시 타입과 참조 타입을 알아봤고 얕은 복사와 깊은 복사도 알아봤어요.

얕은 복사를 하는 방법은 Object.assign(), slice(), 전개 연산자 등의 방법을 통해 얕은 복사를 할 수 있는데 이 중 저는 개인적으로 전개 연산자를 통해 얕은 복사를 하는 것이 가장 좋다고 생각합니다. 다른 방법들로도 구현은 할 수 있지만 해당 방법들의 궁극적인 목적과는 다르고 전개 연산자는 의도가 얕은 복사를 위한 것이기 때문에 좀 더 명확한 코드라고 생각합니다. 

 

위의 예시로 한 깊은 복사의 JSON.parse(JSON.stringify(객체)) 를 사용해도 크게 문제는 없으나 성능이 느리고 함수 Math, date 같은 객체 복사를 할 수 없습니다. 객체 안에 객체와 같은 복잡한 구조나 성능을 고려하여 깊은 복사를 하고 싶다면 lodash 같은 라이브러리를 사용하는 걸 추천드립니다.

 

감사합니다.

 

 

 

반응형
광고차단(애드블록)프로그램이나 브라우저를 사용중이시면 프로그램을 비활성화이나 블로그 주소를 예외 처리 해주시면 광고가 노출됩니다. 귀찮고 번거롭겠지만 광고 클릭은 저에게는 큰 힘이 됩니다. 🙇🏻‍♂️🙇🏻‍♀️
제 블로그의 모든 글은 제가 직접 작성 하고 다른 글을 참고할 때는 이전 글보다 읽기 편하게 수정해서 작성하고 있습니다. 커피 한잔 사먹고 더 열심히 좋은글로 보답하겠습니다.
오늘도 제 블로그에 와 주셔서 감사합니다. :)
//