<코어자바스크립트> 데이터타입

자바스크립트 책리뷰

데이터 타입의 종류

자바스크립트의 데이터 타입에는 크게 두가지가 있다

  • 기본형 타입 (primitive type)
  • 참조형 타입 (reference type)

기본형값이 담긴 주솟값을 바로 복제하는 반면, 참조형값이 담긴 주소값들로 이루어진 묶음을 가르키는 주솟값을 복제한다는 점이 다르다.

기본형은 불변성을 띤다 → 굉장히 낯설었는데 설명을 듣다 보면 이해가 간다.

과거에는 메모리 용량이 부족하여 타입별로 할당할 메모리 영역을 정해서 사용했는데, 현대에 메모리 용량이 월등히 커진 상황에서 자바스크립트는 메모리 관리에 대한 압박에서 비교적 자유로워졌다.

각 데이터들은 비트로 표현이 되어있고, 비트들은 고유한 식별자를 가지고 있다. 비트들의 묶음, 즉 바이트들 또한 비트의 식별자로 위치를 파악할 수 있을 것이다.

모든 데이터는 바이트 단위의 식별자, 즉 메모리 주솟값을 통해 서로 구분하고 연결할 수 있다.

변수 선언과 데이터 할당

변수선언

var a; // 1-1

변수란 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇, 이 공간에 숫자를 담았다가 문자열을 담는 등의 다양한 명령을 내릴 수 있다.

지금부터 이를 바탕으로 “자바스크립트의 데이터 구조 이해”를 명목으로 매우 간단하게 개략적으로 표현을 한다.

메모리 표현에서 깊게 들어가면 이해 안되는 표현들이 좀 있음

주소 5004번지에 ‘abc’를 담는다는데, 그럼 이게 실질적으로 어떻게 ‘abc’ 가 저장된다는거지? 다른 실제 저장 주소가 따로있고, 5004는 그 주소의 오프셋인건가? 그리고 이어지는 주소로 5005 가 있는데 ‘abc’ 중 일부가 5005 주소에도 할당될 수 있는거 아닌가??

그냥 메모리 할당방식을 대략적으로 표현한것이고, 실제 메모리 주소는 이와 다르다는 것만 인지하고 접근하자

1-1 의 명령을 받은 컴퓨터는 메모리에서 비어있는 공간 하나를 확보한다, 그리고 이 공간의 이름(식별자) 를 a라고 지정한다.

이후에 사용자가 a에 접근하고자 하면 컴퓨터는 메모리에서 a라는 이름을 가진 주소를 검색해 해당 공간에 담긴 데이터를 반환한다.

데이터 할당

var a;
a = 'abc';

위와 같이 식별자의 주소공간에 ‘abc’를 직접 저장하지 않는다. 데이터를 저장하기 위한 별도의 메모리 공간을 확보해서 문자열 ‘abc’를 저장하고, 그 주소를 변수의 주소공간에 저장하는 식으로 데이터할당이 이루어진다

왜 그러는걸까? 왜 변수영역에 값을 직접 대입하지 않고, 굳이 번거롭게 한 단계를 더 거치는 걸까? 이는 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위한 고민의 결과이다. 데이터들의 자료형은 각각 할당되는 공간이 다르다. 가령 문자열만 하더라도, 정해진 규격이 없기 때문에 변수영역에 직접 값을 대입하면, 데이터의 크기가 커지게 될때 이 후에 저장된 값들을 전부 뒤로 미뤄줘야하는 복잡성이 생기게된다

그리고 앞서 모든 기본형은 불변성을 띤다고 언급했는데, 위 예시가 이를 잘 설명한다.

자바스크립트에서는 기본형을 할당할때 어떤 변환을 가하든 상관없이 무조건 새로 만들어 별도의 공간에 저장한다. 따라서 위 예시의 경우 ‘abc’ 문자열에 ‘def’를 더하는 연산을 하게되는 경우 기존 ‘abc’ 문자열에 ‘def’를 이어붙이는 것이아니고, ‘abcdef’라는 새로운 공간에 값을 새로 할당하여 해당 주소로 변수영역의 데이터를 바꾼다.

다른 예로 500개의 변수를 생성하여 모든 변수에 숫자 5를 할당하는 상황을 생각해 보자. 각 변수를 별개로 인식하면 500개의 변수공간을 확보해야하는데 숫자는 8바이트가 필요하다. 따라서 4000 (500 * 8) 바이트만큼이 필요하다 하지만 5를 별도의 공간에 한번만 저장하고 각 변수공간에 해당 주소만 입력한다고 하면 어떨까? 주소공간을 2바이트라고 한다면 1008 (500 * 2 + 8) 바이트만 이용하면 된다. 이처럼 변수 영역과 데이터 영역을 분리하면 중복된 데이터에 대한 처리 효율을 높아진다.

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

불변값

변수와 상수를 구분하는 성질은 ‘변경가능성’ 이다. 불변값과 상수를 같은 개념으로 오해하기 쉬운데, 변수와 상수의 변경가능성의 대상은 변수영역의 메모리이다. 즉 한번 데이터할당이 이루어진 변수공간에 다른 데이터를 재할당할 수 있는지가 관건이다.

반면 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리이다.

기본형 데이터인 number, string, boolean, null, undefined, Symbol 은 모두 불변값이다. 한번 데이터 영역에 할당되면 변경이 불가능하기 때문에 새로 할당하여 변수 영역에서 재할당해야한다

이 때 상수는 변수영역에서 재할당이 불가능한 것이고, 그렇지 않은 것들은 재할당이 가능한 것이다.

가변값

기본형 데이터는 모두 불변값이라 했으니 왠지 참조형 데이터는 가변값일 거 같은 기분이 든다.

기본적으로는 참조형 데이터는 가변값이 맞고, 특정 조건에 따라 아닌경우도 있다 (Object.freeze, Object.defineProperty …)

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

기본형 데이터와의 차이는 ‘객체의 변수(속성, 프로퍼티) 영역’이 별도로 존재한다는 점이다. 위 예시를 보면 별도로 할애한 영역은 변수 영역일 뿐 데이터 영역은 그대로 활용하는것을 볼 수 있다. 데이터영역은 불변값이지만 변수에는 다른값을 얼마든지 대입할 수 있다. 이러한 부분이 참조형 데이터는 불변하지 않다(가변값이다) 라고 하는 것이다.

다른 예제

var obj1 = {
  a: 1,
  b: 'bbb'
};
obj1.a = 2;

기존에 5001 번지에 있던 1이 변화하지 않고 새로 5005번지에 2가 할당되어 7103번지에서 그 주소로 바뀐것을 볼 수 있다. 이처럼 기본형 데이터, 메모리영역은 전부 불변값임을 알 수 있다.

참고로 더이상 참조되지 않는 데이터들에 대한 처리는 가비지 컬렉션(GC)라고 추후에 더 설명할 날이 있을 것이다.

일단 이 책에서는 가장 기본적인 알고리즘 Reference-Counting(참조-세기) 알고리즘으로 참고 횟수가 0이 되면 할당해제를 할수 있는 조건이 되었다 판단하고 진행을해주는 식으로 설명한다

오늘날에는 순환문제로 인해 마크앤 스윕(Mark & Sweap) 알고리즘을 사용한다 정도만 알고 있자

중첩객체

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

obj1.arr[1] 을 반환하는 과정은 다음과 같다

@1004 → @5001 → (@7103 ~?) → @7104 → @5003 → (@8104 ~?) → @8105 → @5004 → 4 반환

자바스크립트에서 배열, 객체, 함수, 정규표현식 등등 전부 객체로써 취급을 한다. 따라서 모두 객체가 프로퍼티를 객체 변수영역에 저장하듯 진행이된다. (좀 복잡해보이지만 흐름에 따라 쫓아가다보면 금방 이해가 간다)

obj.arr = 'str'; 처럼 재할당 명령을 하면 어떻게 될까??

@5006에 문자열 ‘str’을 저장하고 그 주소를 @7104에 저장한다.

그러고나면 @5003의 참조카운트가 0이 되므로 가비지 컬렉터의 수거대상이된다. 가비지 컬렉터는 런타임환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거 대상들을 수거한다. 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈공간이 된다.

불변객체

어떤 상황에 따라 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야하는 경우가 종종 발생한다 하지만 객체를 복사하여 변경을 가하면 필연적으로 원본 객체에도 변경이 된다.(프로퍼티들은 같은 주소를 참조하고 있기 때문에)

따라서 이 연결고리들을 끊어주는 방법들이 있는데 두가지 방법을 소개한다

재귀함수 사용

var copyObjectDeep = function(target) {
  var result = {}; // 새로운 객체 (연결을 끊어줌)
  if (typeof target === 'object' && target !== null) {
    for (var prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    result = target;
  }
  return target;
}

JSON 사용

var copyObjectViaJSON = function(target) = {
  return JSON.parse(JSON.stringify(target));
}

JSON을 사용하는 방식이 쉽고 간단하지만 메소드나 숨겨진 프로퍼티들은 모두 무시한다. 따라서 JSON 방식은 httpRequest로 받은 데이터를 저장한 객체를 복사할 때나, 순수한 정보를 다룰 때 활용하기 좋은 방법이다.

Undefined 과 Null

매우 간단하게 설명하면, undefined 에는 자바스크립트에서 자체적으로 출력하는것이 있고, 사용자가 입력하여 출력하게 만드는것이 있는데, 자바스크립트에서 자체적으로 출력하는 undefined만 있게하라고 한다. 즉 undefined 쓰지마! 이말이다.

굳이 같은 의미의 null이 있는데 undefined 를 쓸 필요가 없고, 시스템 자체적으로 반환하는 undefined 와 사용자가 입력하여 반환되는 undefined 가 서로 조금 다르다.

var arr1 = [];
arr1.length = 3;
console.log(arr1);                            // [empty x 3]
 
var arr2 = new Array(3);                      // [empty x 3]
console.log(arr2);
 
var arr3 = [undefined, undefined, undefined]; // [undefined, undefined, undefined]

보통 시스템에서 반환하는 undefined는 순회의 대상이 아니라서, 순회시 제외된다. 하지만 사용자가 입력한 undefined는 순회의 대상이 된다

null 버그 null은 한가지 자바스크립트 자체적인 버그가 존재하는데 typeof null 이 object라는 것이다 따라서 null인지 판단할 때에는 typeof를 사용하면 안되고 일치 연산자 (===) 를 사용하여 판단해야한다.

정리

기본형 데이터는 모두 불변값이다.

상수는 변수가 재할당 불가능하다는 뜻이고, 재할당 불가능하다는 뜻은 한 번 할당된 데이터의 주소값이 변경될수 없다는 의미이다.

참조형 데이터는 가변값이고, 각각 의 프로퍼티들이 참조하는 주소를 바꾸는 것에 대해 가변적이라고 하지만, 해당 주소에 저장되어있는 데이터 자체는 결국은 불변값이다.

따라서 새로운 값을 재할당하는 경우 새로운 공간에 값을 새로 할당하여 새로운 주소를 해당 변수영역에 재할당한다.

요약하자면 데이터자체는 절대 안변하고, 모두 새로 만들어져서, 새로 만들어진 주소가 변수영역에 재할당되는거임 그렇게 재할당이 되고 더이상 참조하지 않는 데이터는 수거대상이 되는 거임

불변객체를 만드는데에는 두가지 방법이 있는데 재귀함수사용, JSON사용 이 있다

undefined 는 사용자가 직접 사용하는것을 지양하고, null을 대신 사용하도록 한다. 단 자바스크립트의 고질적인 버그로 인해 null을 typeof로 검사하면 안되고 일치연산자(===) 로 검사 해야한다