[Javascript] 데이터 타입

Teasan·2022년 7월 9일
0

JavaScript

목록 보기
13/15
post-thumbnail

데이터 타입


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

자바스크립트 데이터 타입은 크게 두 가지로 분류된다.

Primitive Type(기본형)

  • Number
  • String
  • Boolean
  • null
  • undefined
  • Symbol(ES6에서 추가됨)

Reference Type(참조형)
Object(객체)

  • Array
  • Function
  • RegExp
  • Set / WeakSet(ES6에서 추가됨)
  • Map / WeakMap(ES6에서 추가됨) 등

오늘은 기본형과 참조형을 구분하는 이유와 그 차이에 대해 데이터가 메모리상에서 저장되는 형태를 비교하며 함께 살펴볼 것이다.


타입 별 데이터 할당 순서

Primitive Type(기본형)

먼저 기본형에 대한 데이터 할당의 순서를 예시를 통해 알아보자.

  1. 변수 a를 선언하게 되면,
var a;

컴퓨터가 하는 일은 우선 메모리 안에 데이터가 담길 공간을 미리 확보 하는 것이다.

위의 표를 보면, 임의의 공간 @1002 를 확보하고, 이 공간의 '이름'에 식별자 a를 지정하는 걸 볼 수 있다.

var a;
a = "abc";

이후에 a = "abc" 라고 하는 내용을 만나면, (문자열 "abc"a 에 할당하라는 명령어이기 때문에) 컴퓨터에서는 문자열 "abc"를 우선 비어있는 다른 메모리 공간 @5004에 임의로 저장한다.

그리고 이 임의의 공간에 "abc" 를 저장한 @5004 라는 주소를 든 채로 변수 a 가 가리키는 주소(@1002)로 이동한 뒤, @1002의 값에 @5004 주소를 값으로 할당한다.

var a;
a = "abc"; 

a = "abcdef";

만약, 동일한 a 변수에 다른 값을 재할당하고 싶다면 어떻게 될까.

값이 담긴 주소(@5004)를 할당했던 동일한 과정으로 "abc" 와는 별개의 임의의 공간을 확보하고, 그 공간(@5005)에 "abcdef"를 저장한 뒤 변수 a 가 가리키는 주소(@1002)로 이동해서 값에 임의의 저장 주소(@5005)를 값으로 할당 한다.


Reference Type(참조형)

이번엔 참조형 데이터 할당의 순서를 예시를 통해 알아보자.

"변수 선언 후, 할당한다." 의 기본적인 데이터 할당 순서는 기본형 타입에서 동일하다. 다만, 참조형은 여기서 조금 더 복잡한 과정을 거쳐서 데이터를 할당하게 된다. 예시를 살펴보자.

먼저 객체를 값으로 가진 obj 를 선언하게 되면,

var obj = {
	a: 1,
	b: 'bbb'
};

컴퓨터는 이런 순서로 이해하게 된다.

var obj;
obj = {
	a: 1,
	b: 'bbb'
};

선언이 먼저 이루어지고, 그 이후에 할당의 과정을 거치는 것이다. 원본의 코드처럼 선언과 할당을 동시에 써놓았더라도, 컴퓨터는 선언 → 할당 의 순서로 나눠서 이해하고 처리를 하게 된다.

var obj = {
	a: 1,
	b: 'bbb'
};

다시, 원본 코드로 돌아와 참조형 데이터 할당이 어떻게 이루어지는지 순서대로 차근차근 확인해보자. 먼저, obj 를 선언하면, 컴퓨터는 비어있는 임의의 공간을 찾아서

임의의 공간 @1002의 이름을 식별자 obj 라고 지정한다. 이 값으로 할당을 하기 전에 obj 의 데이터를 임의의 공간을 찾아 넣어야 할 것이다. 먼저 데이터를 넣어둘 임의의 공간 @5002을 확보하고, 데이터를 집어넣어야 한다.

var obj = {
	a: 1,
	b: 'bbb'
};

메모리 구조 공간 하나에는 값이 하나씩 밖에 들어가지 않기 때문에, 객체 안에 두 개의 프로퍼티(ab의 값) 의 두 개의 데이터가 모두 한 메모리 구조 공간에 들어가기는 어렵다. 우리가 확보한 임의의 공간인 @5002에 이 두개의 데이터를 통채로 한꺼번에 집어 넣지 못하기에 바로 여기서 원시형 데이터 타입과는 다른 방식으로 데이터를 저장하게 된다.

참조형 데이터 타입의 변수 영역

내부의 데이터가 메모리 구조 공간 하나에는 값이 하나 씩 밖에 들어가지 않기 때문에 여기서 한 단계를 더 거쳐야만 한다.

먼저, 우리가 확보한 임의의 공간인 @5002에는 @7103번 부터 최대한 확보를 한 뒤에(@7103~?)

이렇게 확보된 공간(@7103~?)에 있는 데이터들을 전부 이 객체의 프로퍼티로 되도록 지정한다. 이제 별도의 공간(@7103~?)이 확보가 됐으니, 여기에 두 개의 데이터, 프로퍼티를 하나씩 할당하면 될 것이다.

a 프로퍼티를 @7103 공간에 식별자 이름으로 지정하고, 데이터(1)를 넣어둘 비어있는 메모리 임의의 공간 @5003 을 확보하여 여기에 a 프로퍼티의 값(1)을 넣어둔다. 그리고 @7103 공간에 값으로 주소(@5003)를 가르키도록 한다.

b 프로퍼티도 a 프로퍼티와 동일하게 진행된다. @7104 공간에 식별자 이름으로 지정하고, 데이터('bbb')를 넣어둘 비어있는 메모리 임의의 공간 @5004 을 확보하여 여기에 b 프로퍼티의 값('bbb')을 넣어둔다. 그리고 @7104 공간에 값으로 주소(@5004)를 가르키도록 한다.

이제 객체 안에 있는 프로퍼티 두개(a, b)가 모두 값이 할당이 되었으니 이제 완성된 @7103~? 이라고 저장되어 있는 @5002의 주소를 들고 obj에 할당하면 데이터 할당 과정은 끝이다.

참조형 데이터의 재할당

기본형에 대해서는 데이터를 담은 주소만 한 번만 할당하면 끝이 났다. 하지만 참조형 데이터에서는 이렇게 할당하기 이전에 기본형 보다 한 단계를 더 거치게 된다.

var obj;
obj = {
	a: 1,
	b: 'bbb'
};

이번엔 값의 재할당에 대한 참조형 예시를 보자. obj.a 프로퍼티 값을 2로 바꾼다면 어떻게 될까?

var obj;
obj = {
	a: 1,
	b: 'bbb'
};

obj.a = 2;

우선 우리가 변경하고 싶은 데이터 값 2가 선언되어 있는 데이터가 있는지를 찾고, 만약에 없다면 새로 만들어야 한다. 표를 보면, 데이터에 2라는 값이 저장되어 있지 않다. 그러니까, 비어있는 메모리 임의의 공간 @5005을 찾아서 2를 넣어둔다. 그리고 obj → @5002 → @7103 순서로 이동해서, 프로퍼티 a가 값으로 지정한 주소를 @5003 이 아닌, 2가 담겨있는 @5005로 수정 된다.


값이 변하지 않는 참조형

obj.a 의 값이 @5003에서 @5005로 변경이 되었는데, obj 라는 이름을 가진 값의 주소는 바뀌지 않고 그대로 있는 걸 볼 수 있다. 이전에 기본형 데이터를 변경할 때는 어땠을까?

이전의 값이 들어있는 주소 @5004에서 @5005로 값이 변경이 됐다. 하지만 객체에 있는 프로퍼티 값(a) 을 재할당할 때에는 바꾸기 전과 바꾼 후와 상관 없이 여전히 obj의 값은 그대로 동일한 주소를 가리키고 있는 걸 알 수 있다. 이 두가지 차이점은 무엇을 의미하는 것일까?

바꾸기 전과 바꾼 후에도 obj의 값은 여전히 그대로 동일한 주소를 가리키고 있다. 기본형에 비해서 참조형이 메모리 할당 과정에서 1단계를 더 거치기 때문에 기본형은 바로 값이 변하는 반면, 참조형은 값이 변하지 않는다고 볼 수 있다.

참조형의 중첩 객체의 경우라면 어떨까? 이번에도 예시를 보자.

var obj = {
	x : 3,
	arr : [3, 4]
};

먼저, 빈공간 @1002를 확보하고 이름은 obj 라고 한다.

값을 할당하려고 보니, 이전의 경우처럼 객체 안에 프로퍼티가 한 개 이상인 데이터 뭉치인 걸 알 수 있다. 이전과 동일한 방식으로 임의의 영역인 @5003을 확보한 뒤, 이 안에 @7103~?를 확보한다. 그리고 프로퍼티 x를 @7103에 이름으로 넣고, 비어있는 메모리 임의의 공간인 @5003에 값 3을 집어넣은 뒤, x의 값으로 할당한다.

두 번째 프로퍼티인 arr도 마찬가지다. 먼저 프로퍼티 arr 를 @7104에 이름으로 넣고 값을 저장하려고 보니, 값이 배열 형식이다.

arr : [3, 4]

배열 역시 참조형 데이터다. 그러니 여유롭게 공간을 미리 확보를 해두어야 한다. @5002에 집어넣었던 @7103~? 처럼, 같은 임의의 영역인 @5004를 확보하고, 또 다른 변수 영역인 @8104~? 를 지정해서 충분한 공간을 확보한다.

그런 후에, 배열 안의 index 번호를 각각의 이름으로 지정하고, 다시 데이터 영역으로 이동해서 그 배열 안에 index 번호의 값(3, 4)을 할당한 뒤 값으로 주소를 가리키도록 할당한다. 데이터 영역의 3의 경우, 이미 메모리 공간 안에 들어있으므로 값을 재사용 하는 것이다.

이제 배열에 대한 저장이 완료되었으니, @8104~?를 가리키고 있는 @5004를 들고 arr 을 찾아서 값으로 할당하면 배열에 대한 할당 과정이 끝난다.

그리고 이 모든 할당이 끝남과 동시에 객체에 대한 할당이 끝났으므로, @7103~? 을 가리키고 있는 @5002 번을 들고 obj에 다시 값으로 @5002 를 할당하는 과정이 마무리가 된다.

그렇다면, 이 상태에서 obj.arr에 새로운 값(str)을 재할당할 경우는 어떨까?

var obj = {
	x : 3,
	arr : [3, 4]
};

obj.arr = "str";

먼저 문자열 str이 메모리 영역 상에 존재하고 있지 않으므로, 임의의 비어있는 공간(@5006)에 새로 만들어준다. 그리고 이 @5006 을 든 채로 obj를 찾아가서

obj 가 가리키고 있는 값 @5002로 다시 찾아간 뒤, @5002가 가리키고 있는 @7103~?을 찾아가 .arr 라고 하는 프로퍼티를 찾는다. 그리고 이 arr (@7104) 이 가리키고 있는 값 @5004 대신 새로운 값이 들어있는 @5006으로 교체해준다.

이제 arr 프로퍼티는 @5004 대신에 @5006을 가리키게 됐다. 그렇다면, 이전에 사용했던 메모리 공간인 @5004는 어떻게 되는 것일까? arr 의 값이 바뀌게 되면서 @5004번을 참조하고 있는 대상은 없어졌고 이말인 즉슨, 해당 @5004 번을 참조하고 있는 대상은 0개가 되었다는 뜻이다. 그리고 이것을 보통 '참조 카운트 0' 이라고 표현한다.

참조 카운트가 0이라면

참조를 하고 있는 대상이 몇 개있는지를 셀 때 그 숫자를 바로 '참조 카운트' 라고 한다. 그리고 이 @5004번이 바로 참조 카운트가 0인 메모리가 되었다. 참조 카운트가 0인 메모리들은 가비지 컬렉터의 수집 대상이 되기 때문에, 언젠가 사라지게 될 것이다.

Garbage Collecting

현재의 @5004가 담고 있는 @8104~? 번도 @5004가 사라짐으로 인해서 @5004를 참조하고 있는 대상(@8104, @8105)도 함께 사라지게 된다.

연쇄적으로 참조 카운트가 0이 됨으로써 @5004와 함께 가비지 컬렉팅의 대상이 되어 함께 수거가 될 것이다.

값을 직접 저장하지 않는 이유

var obj = {
	x : 3,
	arr : [3, 4]
};

프로퍼티 x 에 들어갈 숫자 3과, 배열 0번 째에 들어갈 3을 처음부터 @5003에 저장하는 대신에 처음부터 저장할 때 x의 값(@7103)이나 배열 0번 째의 값(@8104)에 직접 3을 넣으면 더 간편하지 않을까? 하는 의문이 생길 수도 있다.

이렇게 되면, @5003 이라는 메모리 공간 하나를 더 낭비할 필요도 없고, 그저 확보된 공간마다 사용하게 되니까 더 나아보이니까 말이다. 하지만, 이런 일은 작은 값일 경우에만 해당되고, 만약 큰 값이라면 큰 문제가 될지도 모른다. 예시를 보자.

var obj = {
	x : '큰 용량을 차지하는 문자열',
	arr : ['큰 용량을 차지하는 문자열', 5]
};

obj.x === obj.arr[0];

아까의 숫자 3 대신에 해당 문자열을 넣었다. 그리고 우리가 조금 더 나아보인다고 생각했던 그 방법대로 각각의 값(x, 배열의 0번째 값)에 직접 할당해주었다. (컴퓨터는 문자에 대해서 컴퓨터가 이해할 수 있는 이진법 숫자로 전환을 한 다음에 비로소 메모리에 저장하기 때문에, 너무 긴 값이 나올 수 있으니, 가상의 값을 문자열로 넣었다. 느낌으로만 비교를 해보자.)

obj.x === obj.arr[0];

값을 직접 저장한 경우(첫 번째 표)에 비교를 한다면 어떨까?

완전히 같은지에 대해 비교 계산이 필요하다면, 컴퓨터는 이진법인 상태(현재는 가상의 문자열로 대신 넣었다.)에서 하나하나 비교를 할 것이다. 그리고 이 과정에서 상당한 성능 저하를 불러올 것이다.

obj.x === obj.arr[0];

반면, 처음에 한 번 저장할 때 비어있는 다른 메모리에 저장해놓고, 그 메모리 주소를 가르키는 방식으로 처리하게 되면 할당하는 시간은 이전보다 조금 걸리지만, 같은 값일 경우 재사용이 가능하고, 동일성이 판단이 되고 난 후에는 이후에는 똑같은 비교 과정이 몇 번을 반복적으로 수행이 되더라도 전혀 비용이 발생하지 않게 된다. 똑같은 주소를 가리키고 있는 한은 당연히 같은 값일 수 밖에는 없기 때문이다.

뿐만 아니라, 값의 주소를 저장하는 경우 의외로 메모리 역시 훨씬 덜 차지하게 된다. 아래의 예시를 보자.

'대충 세어 봐도 30바이트가 넘는 문자열' 이라고 하는 문자열은 30바이트가 넘는 문자열이라고 상상해보자. 만약 이 문자열이 x 개가 있다고 쳤을 때 값을 직접 저장할 경우에는 30바이트 곱하기 x 개가 될 것이다. 반면, 값의 주소를 저장할 경우 30바이트 더하기 x 가 될 것이다.

이렇듯, 값을 직접 저장할 경우보다 값의 주소를 저장할 경우가 (데이터 할당시는 느리지만) 훨씬 비용이 적게 든다는 것을 확인할 수 있다. 그러니, 값의 주소를 저장할 경우는 데이터 할당 시간이 조금 느리더라도, 여러가지 면을 따져봤을 때 손해보다 얻는 이득이 훨씬 크다는 이야기가 된다.

앞에서 값의 주소를 저장할 경우 비교에 비용이 들지 않는다고 했다. 이것은 곧 같은 값이 오직 하나만 존재한다는 뜻이다. 즉, 비교에 비용이 들지 않는다는 것이다. 그리고 이것이 곧 '불변 값'을 의미한다.

불변 값 : 같은 값이 오직 하나만 존재.

아래의 예시를 통해 이 의미를 파악해보자.

변수 복사를 통해 불변값을 이해하기

var a = 10;
var b = a;
var obj1 = { c : 10, d : 'ddd'};
var obj2 = obj1;

데이터 할당에 따른 표이다. 각각의 변수 마다 임의의 데이터 공간에 값을 할당하고, 객체의 경우에는 객체 @5003의 변수 영역(@7103~?)을 마련하여 각각의 프로퍼티에 데이터 공간의 주소를 할당하도록 했다. 같은 값인 경우엔 같은 주소를 할당했다. (재사용)

var a = 10;
var b = a;
var obj1 = { c : 10, d : 'ddd'};
var obj2 = obj1;

b = 15;
obj2.c = 20;

그리고 다시 bobj2.c에 값을 재할당을 하고자 할 때,

b 의 값을 15으로 바꾸면, 빈 메모리 임의의 공간에 15를 새로 만들어주고 → 값이 든 주소 @5004을 든 채로 b (@1003)를 찾아서 값으로 @5004를 할당해줄 것이다.

obj.c의 값을 20으로 바꾸면, 빈 메모리 임의의 공간에 20를 새로 만들어주고 → 값이 든 주소 @5005을 든 채로 obj2(@1005) 를 찾아서 @5003 으로 간 뒤 → c 로 이동하여 여기에 값으로 @5005를 할당해줄 것이다.

이제 데이터를 변경한 동작이 모두 끝났는데, 가리키는 값이 달라진 기본형 데이터 @1003와는 달리, 참조형 데이터인 @1004와 @1005는 여전히 같은 값을 가리킨다. 기본형을 값을 바꿨을 때는 가리키는 값 자체가 바뀐 반면에, 참조형이 있는 값을 바꿨을 때는 여전히 똑같은 객체를 바라보고 있는 것이다.

그리고 그것이 바로 객체를 복사하고 복사한 객체 값을 변경했는데도 불구하고 원본 객체의 값이 같이 바뀌는 이유이다.

정리


기본형 데이터 할당과 재할당의 순서

var a;
a = "abc"; 

a = "abcdef";

  1. 변수 a를 선언하게 되면,
  2. 임의의 메모리 공간에 "abc"를 저장하고, 식별자 a를 찾아서 @5004 주소를 값으로 할당한다.
  3. (재할당) 임의의 공간에 "abcdef"를 저장하고, 식별자 a를 찾아서 @5005 주소를 값으로 할당한다.
  4. 참조 카운트가 0인 @5004는 가비지 컬렉터에 의해 사라진다.

참조형 데이터 할당과 재할당의 순서

var obj = {
	a: 1,
	b: 'bbb'
};

obj.a = 2;

  1. 변수 obj를 선언하게 되면,
  2. 확보한 임의의 데이터 공간인 @5002에서 변수영역인 @7103번 부터 최대한 확보를 한 뒤(@7103~?) 그 값을 주소로 저장하고
  3. 임의의 메모리 공간에 1을 저장하고, 식별자 a를 찾아서 @5003 주소를 값으로 할당한다.
  4. 임의의 메모리 공간에 'bbb을 저장하고, 식별자 b를 찾아서 @5004 주소를 값으로 할당한다.
  5. (재할당) 임의의 공간에 "2"를 저장하고, 식별자obj → @5002 → 식별자 a를 찾아서 @5005 주소를 값으로 할당한다.
  6. 참조 카운트가 0인 @5003은 가비지 컬렉터에 의해 사라진다.

느낀 점

데이터 타입에 대해서는 원래 알고 있다고 생각해왔는데, 이번에 강의와 책을 병행해서 읽으면서 '안다'고 생각하고 넘겨짚는 일이 얼마나 위험한 일인지를 알게 되었다.. 가비지 컬렉션이란 이론적 용어도 처음 알게 되었는데, 재할당 후 이전에 사용하던 데이터가 사라지고 빈 공간으로 초기화 된다는 게 신기했다. 책도 책이지만 ㄹㅇ님께 추천 받은 동명의 강의가 너무 좋아서.. (정재남 쌤 항상 감사합니다..) 앞으로 여러 번 반복해서 볼 생각이다. 솔직히 좀 과할 정도로 정리를 한 것 같지만, 내 머리 속에서도 정리가 잘 안되고 있기 때문에 한 번 더 복기하려는 의도된 행동이니 내 블로그 글을 본 친구들이 놀라지 않았으면 좋겠다 ㅋㅋㅋㅋ 아무튼! 오늘은 데이터 타입을 공부했으니 조만간 실행 컨텍스트를 정리할 테다! 요즘 공부가 너무너무 어렵지만 몰랐던 사실이나 훑으며 지나갔던 기초적인 부분을 위주로 다시 공부하는 중이다. 어찌됐건 무엇을 하던 기본기가 제일 중요하다고 생각하기 때문이다.

✦ 출처


🚨 해당 포스팅은 정재남의 ⌜코어 자바스크립트⌟ 강의와 동명의 책을 베이스로 한 기록입니다.
🔖 표와 그림은 전부 제가 한땀한땀 그린 것입니다.. 불펌 자제 🥸

profile
일단 공부가 '적성'에 맞는 개발자. 근성있습니다.

0개의 댓글