자바스크립트 데이터 타입을 깊게알기

Universe·2022년 11월 18일
0

study

목록 보기
44/49
post-custom-banner

Javascript 객체와 불변성이란 ?

  1. 기본형 데이터와 참조형 데이터

자바스크립트에는 기본형 타입(Primitive) 과 참조형 타입(Reference) 두가지의 데이터 타입 속성이 있다.

두 속성을 나누는 가장 핵심적인 요소는

데이터가 복사되는지, 참조 되는지의 유무이다.

기본형데이터

자바스크립트의 Primitive type 은 언어 구현상 가장 낮은 수준으로 취급된다.

객체도 아니고 메소드도 없다. 이중 문자열(String)은 메소드를 사용할 수 있는데,

자바스크립트에서는 기본형의 문자열을 문자열 오브젝트로 변환하여 사용하므로

문자열 오브젝트에 정의된 메소드를 사용할 수 있는 것이다.

기본형데이터는 저장될 때 메모리의 두 영역을 사용한다.

예를들어,

const numFirst = 1;
const numSecond = 1;

이런식으루 두개의 변수를 할당하면

숫자 자료형 1이 저장된 자료형의 식별자와 주소값, 실제 데이터 두 영역을 생성하게 된다.

이때, 실제 데이터에 어떤 값이 할당되는지는 중요하지 않다.

각각의 주소값을 할당하기 때문에 둘 사이의 관계는 남남이라고 봐도 무방하다.

let a = 1;
let b = 2;

a = b;
console.log(a)
// 2

a = 3;
console.log(a)
// 3

console.log(b)
// 2

이런식으로 a에 b의 값을 담아도 서로에게 영향을 주지 않는다.

이 개념을 정확하게 이해하기 위해서는 변수가 재할당이 되었을 때 어떤 일이 일어나는지 알아야 한다.

let a = 1; 

이런식으로 변수가 생성될 때 변수의 이름과 데이터가 저장될 주소값 / 주소값에 실제 데이터 식으로 저장되는데,

a = 2

변수는 재할당 되었을 때 새로운 주소값 을 생성한다.

변수에 변수를 대입하면,

let b = a

b가 a의 데이터가 담긴 주소값을 참조하게 되는데,

b에 새로운 데이터를 할당할 경우

let b = 3

새로운 주소값을 생성해 데이터를 할당하게 된다.

이는 primitive 한 값은 ‘변경’이 불가능하게 설정되어 있기 때문이다. (불변성)

간단하게 생각해서 위의 예시에서 5000 번 주소의 메모리에

1이라는 데이터를 할당한다고 했을때 컴퓨터 입장에서는

‘어느정도의 메모리를 일단 할당해 놓아야 하는지’ 를 계산해야 한다.

할당한 메모리보다 큰 메모리를 차지하는 자료가 들어온다고 생각해보자.

일일히 메모리가 할당될 때마다 새로운 메모리공간을 할당하는 것은 좋은 방법이 아니다.

그래서 컴퓨터는 두가지의 방법을 쓰는데,

첫째로는 해당 자료형에 할당할 수 있는 메모리를 가능한 한 많이 확보하여 해당 데이터를 저장해두고,

두번째로 자료가 저장되는 데이터가 변경될 때 마다 메모리 주소를 재할당 하는 방법이다.

위의 예시에서 5000 번 주소에는

첫째로 숫자라는 형식(Primitive)에 ‘할당할 가능한 한 많은 메모리’가 할당 되어 있고

둘째로 재할당시 새로운 Primitive 형식에 맞추어 ‘할당한 가능한 한 많은 메모리’가 할당 되는 것이다.

그런데 reference 데이터 타입, 예를들어 배열을 위와같이 ‘할당할 가능한 한 많은 메모리’를 계산하려고 했을 때,

도대체 지금부터 얼마나 많은양의 데이터가 담길지 컴퓨터 입장에서는 계산할 수 없다.

그렇기 때문에 reference 데이터 타입은 참조 라는 방법을 사용한다.

참조형 데이터

let obj = {
	name:'soo',
	age:19
}

다이어그램으로 그리면 쉽게 이해할 수 있을까 싶었는데 아닌 것 같다.

첫번째 줄에는 메모리 주소, 그 다음부터는 들어갈 데이터를 의미한다.

변수에 일단 해당 ‘데이터의 키와 벨류를 매칭할 특정 주소값들을 담은 주소’를 변수 이름과 함께 저장한다.

이 변수의 데이터는 데이터의 실제 값이 아니라 값이 저장되어 있는 주소를 담고 있는 것이 된다.

obj 라는 값의 데이터를 보면 @5000의 주소가 저장되어 있고,

주소를 따라 @5000 으로 가보면 키 값의 주소를 찾을 수 있고

다시 주소를 따라 @8000 에서 키 값을, @8000에 명시된 @5001에서 벨류값을 찾을 수 있다.

아래의 코드를 보면,

let arr = [1,2,3]
let arr2 = arr
arr2[2] = 4
console.log(arr)
console.log(arr2)
// [1,2,4]
// [1,2,4]

분명히 arr 를 복사해온 arr2 에 인덱스 2를 조정했음에도 arr 배열도 바뀐 모습을 볼 수 있는데,

위와 같은 이유이다.

arr2는 arr 배열 내부의 값들에 대한 주소값을 가진것이 되므로 (참조에 의한 할당)

같은 주소값에 있는 데이터를 변경하게 되는셈이다.

위와 같이 주소값을 참조하게 되는 복사를 얕은복사라고 하는데 이와 반대로,

primitive 타입처럼 실제 값을 복사하여 새로운 메모리 주소에 값을 할당하는 복사를 깊은복사 라고 하는 것이다.

reference 타입을 깊은복사 하지 않고 데이터를 가공하면

데이터의 원본에 영향을 미칠 수 있기때문에 주의해야 한다.

reference 타입을 깊은복사하는 방법들은 여러가지가 있는데, 배열의 경우

let arr = [1,2,3]
let arrCopy = [...arr]

arrCopy[2] = 4

console.log(arr)
console.log(arrCopy)

(3) [1, 2, 3]
(3) [1, 2, 4]

spread operator 를 이용하여 배열을 새로 만드는 식으로

간단하게 새로운 주소값을 생성할 수 있다. 깊은복사가 되었다는 뜻.

그런데 오브젝트는 그렇게 간단하지 않다.

Object 를 깊은복사 하는 방법

복사한 객체를 변경하더라도, 원본객체에 영향이 가지 않게 작업해야 하는 경우.

깊은복사를 이용한 ‘불변 객체’ 를 만들어 가공해야 한다.

  1. Object.assign() & spread operator
const obj = { a:1 };
const objCopy = Object.assign({}, obj);
const objCopy2 = {...obj}

objCopy.a = 2;

console.log(obj);
console.log(objCopy);
console.log(objCopy2);

//{a: 1}
//{a: 2}
//{a: 1}

깊은복사가 잘 되는 모습.

그러나 이 방법은 depth 가 1 인 객체에서만 할 수 있는 방법이다.

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

const objCopy = {...obj}

obj.b.c = 100
objCopy.b.c = 100

console.log(obj.b.c) // 100
console.log(objCopy.b.c) // 100

이처럼 depth 가 2인, 객체속에 객체 같은 중복 객체의 경우에는

또 다시 주소값을 참조해버린다.

  1. JSON.parse(JSON.stringify())
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

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

objCopy.b.c = 100;

console.log(obj.b.c) // 2
console.log(objCopy.b.c) // 100

JSON.stringify 메소드로 객체를 문자열로 바꾼 뒤

JSON.parse 메소드로 문자열을 객체로 다시 바꾸는 작업이다.

이렇게 하면 depth 가 2인 객체도 깊은복사를 진행할 수 있다.

하지만 이 경우에도 아주 치명적인 단점이 있는데

JSON.stringify 메서드는 객체 내부의 함수를 인식하지 못한다는 점이다.

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

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

console.log(obj.func)
// ƒ () {
//		console.log(1)
//   }
console.log(objCopy.func);
// undefined

이와같이 복사를 진행하면 함수를 undefined 로 인식한다.

  1. 재귀함수 이용
function copyObj(obj) {
  let result = {};
  for(let key in obj) {
    if(typeof obj[key] === 'object') {
      result[key] = copyObj(obj[key]);
    }else{
      result[key] = obj[key];
    }
  }
  return result;
}

함수안에서 함수를 반복적으로 사용해 새로운 object 를 반환하는 함수

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

const objCopy = copyObj(obj)
objCopy.func = function(){console.log(2)}

obj.func()
// 1
objCopy.func()
// 2

조금 더 간단하게 할 수 있는 방법은

  1. lodash - cloneDeep()
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};
const newObj = _.cloneDeep(obj);

자바스크립트 lodash 라이브러리의 _.cloneDeep 기능을 이용하면 쉽게 깊은복사를 진행할 수 있다.

Lodash

profile
Always, we are friend 🧡
post-custom-banner

0개의 댓글