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

JJ·2023년 4월 12일
4

자바스크립트

목록 보기
1/5
post-thumbnail

자바스크립트는 데이터를 어떻게 관리할까?

컴퓨터가 처리할 수 있는 문자, 숫자, 소리, 그림 따위의 형태로 된 자료.

자바스크립트에서는 데이터를 다루기 위해서 7개의 타입을 제공한다. 큰 범주로 원시 타입(primitive type)과 객체 타입(object/reference type)으로 분류되며, 원시 타입은 변경 불가능한 값이라 하여 immutable value이고, 객체 타입은 변경 가능한 값이라 하여 mutable value라고 한다. 7개의 타입을 세분화하기에 앞서서 여기서 언급되는 데이터의 변경 과 관련되어 짧게 짚고 넘어가 보자

immutable vs mutable

자바스크립트에서는 데이터 타입에 따라 값을 저장하는 방식이 다르다. immutable value(이하 "원시 타입")을 변수에 할당하게 되면 변수(확보된 메모리 공간)에는 실제 값이 저장되고, mutable value(이하 "객체 타입")을 변수에 할당하게 되면 변수에는 참조 값이 저장된다.

때문에, 원시 타입이 할당된 변수(a)를 다른 변수(b)에 할당하게 되면, b에는 원본인 a의 원시 타입 값이 복사되어 전달된다. 이를 값에 의한 전달이라고 한다. 이와 달리 객체 타입이 할당된 변수(c)를 다른 변수(d)에 할당하게 되면, d에는 원본인 c의 참조 값이 복사되어 전달되며 이를 참조에 의한 전달이라고 한다.

이렇게만 보면 무슨 뜻인지 이해하기 어렵다. 예시를 들어보자. 컴퓨터에서 a라는 폴더 내부의 b라는 문서를 복사하여 c라는 문서를 만들었다고 생각해 보자. 이때, c 문서의 내용을 변경하였다고 해서 b 문서의 내용이 변경되지는 않는다. 즉, 원시 타입에서 값에 의한 전달은 두 개의 값이 결국에는 다른 메모리 공간에 저장된 별개의 값이라는 의미이다. 이와 다르게 만약 컴퓨터에서 a라는 폴더의 바로 가기 폴더(b)를 만들었다고 생각해 보자. 이때, 바로 가기 폴더 내부에서 어떠한 파일을 변경하거나 삭제하게 되면, a라는 폴더에서도 해당 파일이 변경되거나 삭제된다. 이러한 경우를 참조에 의한 전달이라고 생각할 수 있다. 때문에, 추후에 자바스크립트를 좀 더 공부하게 되면, 얕은 복사와 깊은 복사에 대해서도 배우게 될 것이다. 이를 정확하게 숙지해야 하는 이유는 객체 타입의 데이터를 다룰 때, 원본 값의 변경이 가능하기 때문에 주의 깊게 다룰 필요가 있기 때문이다.

(실제와 다를 수 있지만, 제 나름대로 쉽게 생각해 보기 위해 예시를 들었습니다. 틀린 부분이 있다면 언제든 수정하겠습니다.)


7가지 데이터 타입

구분데이터 타입설명
원시 타입number정수와 실수의 구분없이 하나의 타입만 존재
string문자열
booleantrue / false
undefinedvar 키워드로 선언된 변수에 암묵적으로 할당되는 값
null값이 없음을 의도적으로 명시하는 값
symbolES6에서 추가됨
객체타입객체, 함수, 배열

1. number type

자바스크립트에서는 다른 언어(C, 자바 등)와는 다르게 정수와 소수의 구분 없이 하나의 숫자 타입만 존재하고, 이를 실수로 처리한다. 즉, 정수로 표시하더라도 이는 실수라는 의미이다. ECMAScript 명세에 따르면, 숫자 타입은 배정밀도 64비트 부동소수점 형식을 따른다고 한다. 배정밀도 64비트 부동소수점 형식 이 무엇인지는 구글링을 통해 알 수 있다. 요즘 핫한(?) chat gpt에 따르면, 배정밀도 64비트 부동소수점 형식으로 숫자를 표현하게 되면, 숫자가 64자리로 표현된다고 생각하면 되는데, 가장 첫 번째 자리는 양수와 음수를 구별하는 목적이고, 뒤의 11자리는 지수 부분으로 소수점의 위치를 조정하는 역할을 하고, 나머지 52자리는 가수 부분으로 실제 수를 표현한다고 한다. 이러한 형식은 실수를 매우 정밀하게 표현할 수 있기 때문에, 처리 속도가 다른 방식에 비하여 느리고, 그 정확도에도 한계가 있어서 매우 작거나 큰 수를 표현할 때, 오차가 발생한다고 한다.
이와 관련된 예시를 한가지 보자.

var sum = 0.1 + 0.2;
console.log(sum === 0.3) // false
console.log(sum) // 0.30000000000000004

우리가 생각한 것과 다르게 0.3뒤에 매우 작은 소수가 붙여서 출력되는데, 이런 이유가 자바스크립트에서는 배정밀도 64비트 부동소수점 형식으로 실수를 표현하기 때문이라고 한다.

자꾸, 앨리스의 토끼굴에 빠지는 느낌이지만, 결론적으로 자바스크립트는 이와 같이 숫자를 한 가지 타입으로 표현하고, 이를 실수로 처리한다.


2. string type

문자열 타입은 텍스트 데이터를 나타내기 위해 사용된다. 자바스크립트에서는 문자열 타입의 데이터를 사용할 때, 따옴표나 백 틱으로 텍스트를 감싸서 사용한다. 일반적으로는 작은따옴표를 많이 사용한다고 한다. 문자열 타입에서 나름대로 중요하다고 생각되는 부분은 작은따옴표를 사용하든, 큰따옴표를 사용하든 상관이 없으나 반드시 감싸주어야 하고 이러한 이유는 텍스트를 감싸줌으로써 자바스크립트 엔진이 해당 텍스트를 키워드나 식별자로 인식하지 않는다는 것이다.

var name = 'khakisage' // undefined
var name = khakisage // ReferenceError: khakisage is not defined

추가적으로 ES6에서 템플릿 리터럴이라는 새로운 문자열 표기법이 도입되었는데, 이에 대해 간략하게 설명한다면,
앞서 언급한 백 틱(``)으로 텍스트를 감싸는 표기법인데, 이를 통해 줄바꿈이 가능하고, 이외에도 백 틱 내부에 ${}을 사용하여 중괄호 내부에 문자열이 할당된 변수를 넣어서 사용할 수 있다.

var firstName = 'jong-jin';
var lastName = 'Kim';

console.log(`my name is ${lastName} ${firstName}.`); // my name is Kim jong-jin.

3. boolean type

불린 타입이라고도 불리는데, 논리적으로 참과 거짓을 표현하기 위해 사용된다. 그 의미에서 알 수 있듯이, 진위 여부에 따라 어떠한 명령을 수행하게 하는 그러니깐, 일반적으로 흐름을 제어하는 조건문에서 많이 사용된다.

const validation = () => {
	if (account.email.includes("@") && account.password.length >= 8) {
      setIsEnabled(true); // a
    } else {
      setIsEnabled(false); // b
    }
};

이전에, 로그인 화면을 구현하면서 이메일과 패스워드가 정해진 조건을 만족하는지에 따라 유효성을 검증하는 간단한 함수를 작성한 예시이다. 여기서는 account.email.includes("@") && account.password.length >= 8라는 조건을 만족해야 즉, true라면 a라는 명령이 실행되고, 그렇지 않다면 b가 실행된다는 의미이다. 이와 같이 boolean type을 사용하여 함수의 흐름을 제어할 수 있다.


4.undefined type

undefined는 일반적으로 개발자가 사용한다기보단 자바스크립트 엔진에 의해서 할당되는 값이다. var 키워드로 변수를 선언하게 되면 초기화 과정이 암묵적으로 동시에 일어나는데, 이때, 확보한 메모리 공간에 undefined를 할당하여 그 공간에 있을 혹시 모를 값을 초기화해주는 역할을 한다. 그렇다면, 변수에 값이 없다는 것을 명시하기 위해서는 어떻게 해야 할까? 바로, 이 경우 null type의 데이터를 사용한다.


5. null type

null type의 데이터는 null뿐이다. null은 의도적으로 즉, 개발자가 명시적으로 해당 변수에 값을 없애기 위해 사용한다. 변수에 null을 할당한다는 의미는 다시 말해서, 이전에 참조하던 값을 더 이상 참조하지 않겠다는 의미이고, 따라서 null을 할당하게 되면, 자바스크립트 엔진에 의하여 가비지 컬렉션 이 실행되어 메모리 성능을 높이게 된다.

그렇다면, null은 어떠한 경우에 사용하면 좋을까?

본론부터 말하자면, 클로저 함수로 야기될 수 있는 메모리 성능 저하를 개선하기 위해 사용한다. 추후 다뤄볼 주제이지만, 클로저 함수라는 것이 있다. 클로저 함수에 대해서 아주 짧게 요약하자면, 클로저 함수는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이라는 의미이다. 사실, mdn 문서에서도 이와 동일하게 설명되어 있지만, 처음 이 문장을 보고 바로 이해하기란 너무나도 어려웠다. 아래 예시의 이해를 위해 잠시 렉시컬 환경에 대해 알아보자.

렉시컬 환경(이하 '렉시컬 스코프')이란?

자바스크립트의 경우 함수가 선언된 위치에서 그 함수의 스코프가 결정되고, 이를 기준으로 상위 스코프를 결정하게 되고, 이렇게 결정된 스코프를 렉시컬 스코프라 한다.

각설하고, 다음의 예시를 살펴보자.

1. const x = 1; 
2. function outer() {
3.	const x = 10;
4.	const inner = function() { console.log(x); }; 
5.	return inner;
6.  }
7. const innerFunc = outer();
8. innerFunc();

위의 코드를 살펴보면, 우선, 전역에 x라는 값이 선언되어 1이라는 값이 할당되었고, 외부 함수인 outer() 내부에 중첩함수인 inner()가 선언되어 있는 형태이고, 외부 함수의 바깥에서 innerFunc라는 변수에 outer() 함수가 할당되어 있다.

클로저를 생각지 않고 단순하게 생각해 본다면,

  1. 7번 라인에서 outer() 함수가 호출되면, outer() 함수는 inner 함수를 반환하며 생명주기가 종료된다.(생명주기가 종료된다 = 메모리에서 해제될 예정)
  2. 따라서, outer() 함수의 실행이 종료되었으므로, outer 함수는 자바스크립트 엔진의 실행 컨텍스트의 콜스택에서 제거된다.
  3. 이와 함께, outer 함수 내부의 지역변수인 x(값: 10)도 그 생명 주기가 종료된다.
  4. 하지만, 결과적으로 마지막 줄의 innerFunc()의 실행 시 outer 함수 내부의 x의 값인 10이 출력된다.

클로저를 생각한다면,

  1. 7번 라인에서 outer() 함수가 호출되면, 2번 라인에서 outer() 함수가 실행되고, 이를 통해 5번 라인과 같이 inner() 함수가 반환되며, outer() 함수는 종료된다.(즉, 메모리에서 해제될 것이다.)
  2. 이때, inner() 함수의 렉시컬 환경 즉, 그 상위 스코프인 outer() 함수의 블록 스코프가 렉시컬 스코프가 된다.
  3. 때문에, 마지막 줄에서 innderFunc()가 호출되면, 결국 inner() 함수가 호출되고, 이때, inner() 함수는 여전히 outer 함수의 블록 스코프를 렉시컬 스코프로 참조하므로 outer 함수의 블록 스코프에서 재할당된 값이 10인 x가 출력되게 된다.

따라서, 다음의 코드를 다시 살펴보자.

1. const x = 1; 
2. function outer() {
3. 	const x = 10;
4.	const inner = function() { console.log(x); }; // 2
5.	return inner;
6. }
7. const innerFunc = outer(); // 3
8. innerFunc(); // 4
9. outer = null; // 5
10.console.log(inner()) // 6

위와 동일한 코드이지만, 10번 라인에서 outer에 null 값을 할당하여 명시적으로 빈 값으로 처리를 하여 여전히 참조되고 있는 inner() 함수의 렉시컬 스코프를 끊어준다. 때문에, 10번 라인을 실행 시, ReferenceError와 함께 inner가 정의되지 않았다는 메시지가 출력된다.

설명이 길었는데, 결과적으로 클로저 함수를 자주 사용하게 되면, 메모리의 성능 저하가 야기될 수 있다. 때문에, null을 사용하여 다음과 같은 성능 저하를 방지할 수 있다고 한다.


6. symbol type

symbol type은 ES6에서 새롭게 추가된 타입으로, 변경 불가능한 원시 타입이다. 다른 값과 중복이 되지 않는 유일무이한 값으로 설명되고 있는데, 일반적으로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해서 사용한다고 한다.

const key1 = Symbol('hi');
const key2 = Symbol('hi');
// Symbol함수를 사용하여 둘다 동일한 hi라는 프로퍼티 키를 생성하였다.
console.log(key1 === key2) // false
// 유일무이한 값으로 일치하지 않음을 알 수 있다.

이와 같이, 아직으로썬 그 사용법이 와닿진 않지만, 한 가지 가정을 해보자면, 만약 회원의 정보를 담는 객체를 생성하고, 해당 객체에 회원의 정보 중 취미를 담기 위해 그 프로퍼티 키로 'hobby'를 생성하였다고 가정해 보자. 그런데, 만약 다른 사람이 이후에 작업을 하면서 hobby라는 키가 없다고 착각하고, 새롭게 생성을 하게 된다면 문제가 발생할 수 있다. 때문에, symbol을 사용하여 다른 값과 일치되지 않도록 사용한다고 생각해 볼 수 있을 것 같다.

symbol type의 특이한 점은 다른 원시 타입의 경우 리터럴을 통해서 생성하지만, symbol의 경우 Symbol()이라는 함수를 통해서 생성된다고 한다. 이후에, symbol에 대해서 배우고, 자세하게 블로깅을 하려고 한다.


7. object/reference type

앞서, 언급하였듯이 자바스크립트는 큰 범주로 원시 타입과 객체 타입으로 분류되며, 각각 변경 불가능하냐 변경 가능하냐의 차이가 있다. 추후에 좀 더 자세하게 블로깅을 진행할 예정이며, 지금으로썬 그저 객체 타입을 사용하게 되면, 여러 가지 타입의 데이터를 하나로 그룹화하여 관리할 수 있는 데이터의 종류라고 생각하고 넘어가자.


글을 마무리 하며..

팀원들과 스터디를 진행하면서, 개인적으로 다시 한번 데이터 타입에 대해 정리해 보려고 블로깅을 하게 되었다. 작성하다 보니 말을 늘이는 습관이 있어서 어쩌다 보니 생각보다 글이 길어졌는데, 이제는 어느 정도 알겠다고 생각되는 데이터 타입이 있는 반면, 아직도 그 실체가 명확하지 않게 생각되는 데이터 타입도 있었다. 결론적으로, 자바스크립트를 잘 활용하기 위해서는 데이터 타입을 제대로 알고 넘어가야 한다고 생각되었고, 때문에 여러모로 유익한 글쓰기 시간으로 생각되었지만, 한편으론, 다시 한번 블로그 작성에 있어서 신뢰성의 문제를 생각해 볼 수 있었다. 정확한 정보 전달을 위해 노력했지만, 예시나 설명에서 분명 틀린 부분이 있을 거라고 생각된다. 다음의 나는 좀 더 정확한 정보를 전달할 수 있으리라 믿으며 글을 마쳐본다.

profile
한줄 한줄

3개의 댓글

comment-user-thumbnail
2023년 4월 12일

한 번 스터디로 공부한 내용인데도 다시 봐도 헷갈리는 부분이 많네요 ㅠㅠ 유익한 글이었습니닷!!

1개의 답글
comment-user-thumbnail
2023년 4월 13일

클로저... 이해하기 너무 어렵네요...💧

답글 달기