자바스크립트의 데이터 저장방식과 타입

휴먼스케이프

책 [코어 자바스크립트]를 기반으로 작성하였습니다.

안녕하세요, 휴먼스케이프 프론트엔드 개발자 Tasha입니다. 먼저 글의 취지를 말씀드리자면, 저희 Tech팀에서는 원하는 관심사 분야의 사람들끼리 모여 세미나를 진행하기로 했는데요. 그 중 ‘Javascript를 제대로 공부해보자!’의 취지를 가진 사람들끼리 모여 책 [코어 자바스크립트]를 기반으로 스터디를 진행하기로 결정하였고, 그 기록을 남기고자 합니다.

첫 번째 주제는 자바스크립트의 ‘데이터 타입'입니다.

[출처]: https://virtualizationreview.com/articles/2015/06/18/bluedata-supports-hadoop-and-spark-on-docker.aspx

데이터는 어떤 형태로 저장할까요? 컴퓨터는 데이터를 0과 1로 바꿔 저장하는데, 이를 효율적으로 식별하고 관리하기 위해 묶어서 표현한 것이 byte단위입니다. 정적타입의 언어에서도 메모리의 낭비를 최소화하면서 데이터를 저장하기 위해 데이터를 타입으로 분류하고, 해당 타입별로 할당이 가능한 byte를 지정하도록 하였습니다. 예를 들어, short 타입으로 지정된 변수에 일정 byte 이상의 수를 할당을 하면 오류가 나고, 이를 int타입으로 형변환해야 할당이 가능하도록 강제한 것입니다.

자바스크립트의 경우에는, 메모리 용량이 과거보다 월등히 커진 상황에서 등장했기 때문에 메모리관리의 압박에서 자유로워졌습니다. 따라서 숫자를 정수형/부동소수형과 같은 형태로 구분하지 않고, 변수에 타입을 강제하지 않아 사용이 자유로운 특징이 있습니다. 그러나 자바스크립트에서도 데이터를 다룰 때에 타입에 따른 특징이 있고, 특정 연산자에 의해 강제변환과 같은 상황이 이루어지기도 하므로 타입에 대해서 알아볼 필요가 있습니다.

1. 자바스크립트의 메모리 저장 방식

자바스크립트 코드에서는 ‘변수'에 ‘값'을 할당하는 방식으로 데이터를 다루며, 변수와 값을 각각 메모리 영역에 저장합니다.

var hundred = 100;

여기서 값에는 타입이 존재하고, 변수에는 타입이 존재하지 않습니다. 자바스크립트에서는 타입 강제를 하지 않기 때문에 변수는 어떠한 타입의 값이라도 가질 수 있습니다.

[출처]: solmii velog

위와 같은 형태로 변수를 선언할 때에는 변수영역의 메모리에 선언하고자 하는 변수가 없으면 빈 공간(ex. 주소 1002)을 확보하며 공간에 변수를 저장하게 됩니다. 값은 데이터영역의 메모리의 빈 주소 공간에 할당이 되며 변수영역은 이 데이터영역의 주소 값을 가지고 있게 됩니다.

이렇게 변수와 값을 나누어 저장하는 이유는 타입을 강제하지 않는 자바스크립트 특성과 관련이 있습니다. 문자/숫자 타입에 따라 필요한 용량이 가변적인데, 한 공간 내에서 형변환이 이루어져 공간의 크기를 조정해야하면 복잡해지기 때문입니다. 예를 들어, 중간에 있는 데이터를 늘려야한다면 저장된 데이터들을 전부 뒤로 옮기고 식별자에 연결하는 일을 다시해야하는 번거로움이 발생합니다. 따라서 이를 효율적으로 다루기 위해 변수와 데이터를 별도의 공간에 나누어 연결짓는 방식으로 저장하는 것입니다.

자바스크립트의 [값]에서 다루는 타입은 총 7가지로, 다음과 같습니다.

Number, String, Boolean, null, undefined, Symbol, Object

이 타입들은 메모리에 할당되는 형태에 따라 크게 기본형과 참조형으로 나눌 수 있습니다.

2. 기본형 타입의 데이터 할당 방식

먼저 기본형 타입의 경우에는 위의 그림과 같은 형태로 데이터가 할당이 되는데, 기존에 있던 변수에 새로운 값을 할당한다고 가정하면 다음과 같은 일들이 일어납니다.

새로운 데이터 주소 공간 확보

해당 주소에 값 할당

기존 변수에 새 주소를 할당

이렇게 기존 값에는 영향을 미치지 않고 새 값을 할당하는 방식으로 이루어지기 때문에, 데이터가 변경되지 않아 불변성을 유지합니다. 이와 같은 방식으로 저장하는 데이터를 ‘기본형 데이터'라고 지칭하고, 이 데이터에는 다음과 같은 타입들이 속합니다.

Number, String, Boolean, null, undefined, Symbol

자바스크립트의 총 7가지 타입에서 1가지 Object만 빠져있습니다. Object는 기본형 타입이 아닌 ‘참조형 타입'의 데이터입니다.

3. 참조형 타입의 데이터 할당 방식

기본형 데이터와 다르게 참조형 데이터에는 ‘객체 변수(프로퍼티) 영역'이 별도로 존재합니다. 객체 변수 영역에 각각의 프로퍼티를 저장하고, 이 주소들을 데이터영역에서 참조하여 변수에 할당되는 형태로 저장이 되는 방식입니다.

[출처]: solmii velog

이 데이터타입에서 다음과 같은 데이터할당이 일어난다고 가정해보겠습니다.

var obj2 = obj;
obj2.age = 25;
obj1.age; // 25

변수 영역에 빈공간에 obj2가 할당

해당 변수 영역의 값에 obj와 동일한 주소값이 할당됨

데이터영역 빈공간에 25가 할당

7002번 주소값이 값 25가 들어있는 주소를 가리킴

변수 영역에는 어떠한 값도 할당할 수 있기 때문에 객체 변수 영역이 다른 값을 가리킬 수 있습니다. 따라서 obj1의 값이 가리키고 있는 주소는 변하지 않았지만 기존의 객체 내부의 값은 바뀌게 되므로, 참조형 데이터는 ‘가변값'이라고 하는 것입니다.

이렇게 값이 데이터의 주소를 참조하고 있기 때문에 참조형데이터라고 불리며, 객체 뿐만 아니라 객체의 하위 타입(array, function, date, regexp) 등도 참조형 타입에 속하게 됩니다.

4. 불변성을 지키는 참조형 데이터

객체는 참조형 데이터로, 위와 같은 방식으로 데이터를 복사하여 프로퍼티를 변경하면 기존 프로퍼티 값에 영향을 미치게 됩니다. 따라서 기존 프로퍼티에 영향을 미치지 않고 복사한 값을 독립적으로 사용하려면 복사를 할 때 기존 객체의 프로퍼티를 복사한 새 객체를 만들어주는 방식으로 진행해야합니다.

데이터의 프로퍼티 값에 기본형타입만 할당되어 있는 경우

var numArray = [1, 2, 3];
var stringObj = { en: 'hello', kr: '안녕' };
var copiedNewArray = numArray.slice();
var copiedStringObj = { ...stringObj };
copiedNewArray[0] = 0;
newArray[0] === copiedNewArray[0]; // false
copiedStringObj.en = 'hi';
copiedStringObj.en === stringObj.en; // false

참조형데이터의 프로퍼티에 기본형 데이터 값만 들어있는 경우에는 위와 같이 Array 내장 메소드나 간단하게 sperad operator를 활용하여 불변성을 유지한채 복사가 가능합니다.

2. 데이터의 프로퍼티 값에 참조형타입이 할당되어 있는 경우

참조형 타입이 할당되어 있는데 위와 같은 형태로 복사를 진행하면 불변성을 유지하지 않습니다.

var p = { abc: '123' };
var stringArray = [ p ];
var stringObj = { p };
var copiedNewArray = stringArray.slice();
var copiedStringObj = { ...stringObj };
copiedNewArray[0].abc = 456;
stringArray[0] === copiedNewArray[0]; // true
copiedStringObj.p.abc = 456;
copiedStringObj.p.abc === stringObj.p.abc; // true

프로퍼티의 값이 참조형일 경우, 값이 특정 주소를 가리키고 있습니다. 이의 변수 값을 변경하면, 원래 참조형 데이터가 가리키는 주소의 변수 값도 바뀌므로 불변성을 유지하지 않게 되는 것입니다.

따라서 이렇게 중첩된 구조로 참조형 프로퍼티가 들어있는 경우에는 깊은복사(deep copy)를 해주어야 하는데, deep copy를 이용하는 방식에는 다음과 같은 방법들이 있습니다.

1. 객체의 깊은 복사를 수행하는 함수 이용

: 객체의 프로퍼티를 순회하면서 참조형일 경우 재귀를 돌면서 복사를 해주는 방식입니다.

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 result;
};

2. JSON 활용

json을 문자열형태로 변환한 후 복사하여 다시 json화하는 형태로 사용하는 방법입니다.

var obj2 = JSON.parse(JSON.stringify(obj1));

이렇게 하면 깊은복사가 진행되지만, JSON.stringify의 특성상 값이 undefined, 함수, 심볼인 경우에는 null로 반환되는 특성이 있고, 성능상 느리다는 단점이 있습니다.

3. spread operator 이용

spread operator를 프로퍼티 값이 참조형 타입일때마다 수동으로 중첩된 구조에서 잘 활용하면 deep copy를 진행할 수 있습니다.

var p = { abc: '123' };
var obj = { a: { b: p, c: 2 }, aa: 1 };
var copiedObj = { ...obj, a: {...obj.a, b: {...p} } };

4. Immutable.js, Immer.js, Lodash와 같은 라이브러리 활용

위의 성능상의 이슈나 번거로움 등의 문제가 있어 deep copy를 위해 위와 같은 라이브러리를 활용하기도 합니다.

지금까지 코어자바스크립트 첫번째 챕터에 해당하는 자바스크립트의 타입 특성에 대해 알아보았습니다. 지금까지 큰 분류에서의 데이터 타입을 알아보았는데, 자바스크립트에서 데이터를 타입 특징에 맞게 잘 활용하기 위해 각 하위 타입별 특성을 더 공부해봐도 좋을 것 같습니다 .

앞으로도 코어자바스크립트를 기반으로한 자바스크립트 내용에 대한 포스팅이 올라올 예정이니 기대해주세요🙂

[출처]

메모리 관련 이미지 출처

책 [코어 자바스크립트]

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/