모던 자바스크립트 Deep Dive : 6장 데이터 타입

코코딩딩·2022년 7월 14일
0
post-thumbnail

데이터 타입

데이터 타입은 값의 종류를 의미한다.자바스크립트의 모든 값은 데이터 타입을 갖는다. ES6에서는 7개의 데이터 타입을 제공한다.7개의 데이터 타입은 원시타입(primitive type)과 객체 타입(object / reference type)으로 분류할 수 있다.

참고:
ECMAScript2020(ES11)에서는 새로운 원시값 Bigint가 추가되었다.
Bigint를 사용하면 JavaScript 에서도 BigDecimal을 구현할 수 있다.
(이미 많은 라이브러리가 존재한다)


숫자타입

C나 Java에서는 정수(int/long), 실수(float/doubl)를 자료형을 제공한다. 하지만 자바스크립트는 숫자타입만을 제공한다.
ECMAScript 사양을 따르면 number type은 배정밀도 64비트 부동소수점 형식을 따른다. 이는 Java의 double type과 같은 형식을 따른 다는 것을 알 수 있다.(IEEE754 참고)

var integer = 10; // 정수
var double = 10.12; // 실수
var negative = -20; // 음의 정수

위 코드 모두 숫자타입이다. 정수, 실수, 2진수, 8진수, 16진수 리터럴은 모두 메모리에 배정밀도 64비트 부동소수점 형식의 2진수로 저장된다. 또한 숫자타입 값은 모두 10진수로 해석된다.

var binary = 0b01000001;//2진수
var octal = 0o101; //8진수
var hex = 0x41; //16진수

// 표기법만 다를 뿐 모두 같은 값이다.
console.log(binary); //65
console.log(octal); //65
console.log(hex); //65
console.log(binary === octal) //true
console.log(octal === hex) // true

자바스크립트는 정수타입을 따로 제공하지 않기에 모든 숫자타입 값은 실수로 처리한다. 이는 정수로 표시해도 사실 실수라는 것을 의미한다. 따라서 정수로 표시되는 수끼리 나누더라도 실수가 나올 수 있다.

//숫자타입은 모두 실수로 처리된다.
console.log(1 === 1.0); //true
console.log(4 / 2); //2
console.log(3 / 2); //1.5

숫자 타입은 추가적으로 세 가지 특별한 값도 표현할 수 있다.

  • Infinity : 양의 무한대
  • -Infinity : 음의 무한대
  • NaN: 산술 연산 불가(not-a-number)
//숫자 타입의 세 가지 특별한 값
console.log(10 / 0); //Infinity
console.log(10 / -0); //-Infinity
console.log(1 * 'String'); //NaN

문자열 타입

문자열 타입은 텍스트 데이터를 나타내는데 사용한다.
문자열은 작은따옴표(''), 큰따옴표(""), 백틱으로 텍스트를 감싼다.

var string;
string = '문자열';
stirng = "문자열";
stirng = `문자열`;//(ES6부터 지원)
string = '작은따옴표로 감싼 문자열 내의 "큰따옴표"는 문자열로 인식된다.'
string = "큰따옴표로 감싼 문자열 내의 '작은따옴표'는 문자열로 인식된다."

문자열을 따옴표로 감싸는 이유는 키워드나 식별자 같은 토큰과 구분하기 위함이다.
또한 따옴표로 문자열을 감싸지 않는다면 공백문자도 포함할 수 없다.

var string = hello;//ReferenceError: hello is not defined
var string2 = hello world; // hello라는 식별자와 world라는 식별자를 나열한 것으로 문법에러

C언어에서는 문자열은 문자의 배열료 표현하고, Java에서는 객체로 표현한다. 하지만 자바스크립트의 문자열은 원시타입이며 변경 불가능한 값(immutable value)이다.


탬플릿 리터럴

ES6부터 템플릿 리터럴이 도입이 되었다. 템플릿 리터럴은 멀티라인 문자열, 표신식 삽입, 태그드 템플릿과 같은 편리한 문자열 처리 기능을 제공한다. 템플릿 리터럴은 런타임에 일반 문자열로 변환되어 처리된다.

템플릿 리터럴은 백틱을 사용해 표현한다.

var template = `template literal`;
console.log(template);//template literal

멀티라인 문자열

일반 문자열 내에서는 줄바꿈이 허용되지 않는다.

var str = 'hello
world';
// SyntaxError : Invalid or unexpected token

따라서 일반 문자열 내에서 줄바꿈 등의 공백을 표현하려면 이스케이프 시퀀스를 사용해야한다
줄바꿈 문자는 \n, 탭은 \t이다.

var template = '<ul>\n\t<li><a href="#">Home</a></li>\n</ul>';
console.log(template);

위 코드의 출력 결과는 다음과 같다.

<ul>
	<li><a href="#">Home</a></li>
<ul>

일반 문자열과 달리 템플릿 리터럴을 사용하면 이스케이프 시퀀스를 사용하지 않아도 위와 같은 출력 결과를 얻을 수 있다.

var template = `<ul>
	<li><a href="#">Home</a></li>
</ul>`;
console.log(template);

표현식 삽입

문자열 중에서 특정 내용만 변수처리를 하고 싶을 때 사용할 수 있다. ES5스팩 까지는 해당 기능이 제공되지 않아 문자열 연결 연산자를 사용해 이를 출력하였다.

var first = 'Ung-mo';
var last = 'Lee';

//ES5: 문자열 연결
console.log('My name is ' + first + ' ' + last + '.'); //My name is Ung-mo Lee.

템플릿 리터럴 내에서 표현식 삽입을 통해서 간단하게 동일한 내용을 출력할 수 있다. 이는 문자열 연산자보다 가독성도 뛰어나다.

var first = 'Ung-mo';
var last = 'Lee';

// ES6: 표현식 삽입
console.log(`My name is ${first} ${last}.`); // My name is Ung-mo Lee.

표현식을 삽입하려면 ${}으로 표현식을 감싼다. 이때 표현식의 평가 결과가 문자열이 아니더라도 타입이 문자열로 강제 변환되어 삽입된다. 표현식 삽입은 반드시 템플릿 리터럴 내에서 사용 해야한다. 그렇지 않으면 일반 문자열로 취급한다.


불리언 타입

불리언 타입의 값은 논리적 참, 거짓을 나타내는 true와 false뿐이다.

var foo = true;
console.log(foo); //true

foo = false;
console.log(foo); //false

불리언 타입의 값은 참과 거짓으로 구분되는 조건에 의해 프로그램의 흐름을 제어할 때 사용한다.


undefined 타입

undefined 타입의 값은 undefined가 유일하다.
이전 챕터에서 살펴보았 듯, var 키워드로 선언한 변수는 암묵적으로 undefined로 초기회 된다. 값을 저장하기 위해 메모리에 공간을 확보할 때 해당 공간을 빈 값(대부분 쓰레기 값이 들어있음)으로 내버려두지 않고 undefined로 초기화 한다. 따라서 변수를 선언하고 아무런 할당없이 해당 변수를 참조하면 undefined가 반환된다.

var foo;
console.log(foo); //undefined

undefined를 직접 할당하는 것은 권장하지 않는다. undefined는 자바스크립트 엔진이 변수를 초기화 할때 사용하는 값이므로 이를 통해서 어떤 변수를 참조할 때 undefined가 반환된다면 선언 후 한번도 할당되지 않았다는 것을 알 수 있다. 이것이 undefined의 본래 취지이며 이를 임의로 사용하는 것은 좋지 않다.

만약 변수에 값이 비어있다는 것을 의미하고 싶다면 null을 할당하면 된다.


null 타입

null 타입의 값은 null이 유일하다. 프로그래밍 언어에서는 null을 변수에 값이 없다는 것을 의도적으로 명시할 때 사용한다. 이를 의도적 부재라 한다. 변수에 null을 할당한다는 것은 변수가 이전에 참조하던 값을 더 이상 참조하지 않겠다는 의미다.

var foo = 'Lee';

// 이전 참조를 제거. foo 변수는 더이상 'Lee'를 참조하지 않는다.
// javascript는 C언어 처럼 메모리를 개발자가 직접 관리하지 않기 때문에 
// null을 할당한다고 곧바로 foo에 할당되어있던 'Lee'의 메모리가 릴리즈 되지는 않는다.
// 이는 가비지컬랙터의 동작에 대한 이해가 필요하다.
foo = null;

또한 함수가 유효한 값을 반환할 수 없는 경우 명시적으로 null을 반환하기도 한다.


심벌 타입

ES6 스팩에서 추가된 7번째 타입으로, 변경이 불가능한 원시 타입의 값이다. 심벌 값은 다른 값과 중복되지 않는 유일무이한 값이다. 따라서 주로 객체의 프로퍼티 키로 사용한다. 키 값으로 심벌 타입의 값을 사용하면 키가 충돌할 일이 없어진다.

var key = Symbol('key');
console.log(typeof key); // Symbol

//객체 생성
var obj = {};

// 이름이 충돌되지 않도록 심볼을 프로퍼티 키로 사용한다.
obj[key] = 'value';
console.log(obj[key]); // value

객체타입

자바스크립트의 데이터 타입은 크게 원시타입과 객체타입으로 분류할 수 있다. 원시타입과 객체타입은 근본적인 차이가 있기 때문이다. 이에 대한 자세한 내용은 11장에서 다시 다룬다.


데이터 타입의 필요성

데이터 타입에 의한 메모리 공간의 확보와 참조

값은 메모리에 저장하고 참조할 수 있어야한다. 그런데 메모리에 값을 저장하려면 먼저 확보해야할 공간의 크기를 정해야한다. 즉, 몇 바이트의 메모리 공간을 사용해야 낭비와 손실 없이 저장할 수 있는지 알아야한다.

var score = 100;

위 코드가 실행하면 100에 해당하는 값을 저장하기 위해서 공간을 확보하고 숫자 값 100을 2진수로 저장하게 된다. 즉 먼저 공간의 크기를 적절히 정해야지 값을 저장할 수 있는 것이다.
자바스크립트 엔진은 값의 데이터 타입에 따라서 정해진 크기의 메모리 공간을 확보한다. 즉, 변수에 할당되는 값의 데이터 타입에 따라서 확보해야할 메모리 공간의 크기가 결정된다.

자바스크립트 엔진에서는 숫자타입의 값을 저장하기 위해서 8바이트의 메모리 공간을 확보한다. 그리고 100을 이진수로 저장한다. 아래의 그림을 참고하자.

자바스크립트 엔진은 숫자타입의 값을 생성할 떄 배정밀도 64비트 부동소수점 형식을 사용한다. 따라서 실제 메모리에 저장되는 이진수의 값은 위 그림과 다르다.

이제 이 값을 참조하는 경우를 생각해보자. 식별자 score를 통해서 숫자 타입의 값 100이 저장되어있는 메모리 공간의 주소를 찾아갈 수 있다. 정확히 말하면 메모리 공간의 선두 메모리 셀의 주소를 찾아갈 수 있다. 그러면 선두 메모리부터 데이터 타입에 따라 정해진 offset만큼의 메모리가 100에 대한 이진수를 저장한 공간일 것이다.

숫자 타입은 8바이트이므로 만약 8바이트를 읽어들이지 않는다면 데이터 손실이 일어날 것이다. 자바스크립트 엔진은 score라는 변수에 숫자 타입이 할당되어 있는 것을 인식하고 8바이트 단위로 메모리에 있는 값을 읽을 것이다.

결국 데이터 타입이 존재하므로 메모리에 정해진 크기의 공간을 확보할 수 있고, 이에 따라 참조할 수 있다.

데이터 타입에 의한 값의 해석

데이터를 효과적으로 저장하고, 손실 없이 읽는 것 외에 다른 문제가 더 있다. 위에서 저장한 100에 대한 이진수를 다시 읽어올 때 어떻게 해석하느냐 하는 문제이다. 만약 메모리에 0100 0001이 저장되어있고 이를 숫자로 해석하면 65이지만, 문자열로 해석하면 'A'이다. 이 때 어떤 데이터 타입의 값이냐에 따라 해석이 정해진다.

데이터 타입의 필요성 정리 :

  • 값을 저장할 때 확보해야 하는 메모리 공간의 크기를 결정하기 위해
  • 값을 참조할 때 한 번에 읽어 들어야 할 메모리 공간의 크기를 결정하기 위해
  • 메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해

동적 타이핑

자바스크립트의 모든 값은 데이터 타입을 가진다. 하지만 변수는 데이터 타입을 가지지 않는다.

동적 타입 언어와 정적 타입 언어

C나 자바 같은 정적 타입 언어변수를 선언할 때 해당 변수에 할당할 수 있는 데이터 타입을 사전에 선언해야 한다. 이를 명시적 선언이라 한다.

//java의 예시
int i = 0;
char c = 'c';

정적 타입 언어는 변수의 타입을 변경할 수 없으며, 변수에 선언한 타입과 맞는 값만 할당 가능하다. 정적타입 언어는 컴파일 시점에 타입체크를 수행한다. 만약 이를 통과하지 못한다면 프로그램 자체가 실행되지 않는다. 이를 통해 타입의 일관성을 강제하므로 안정적인 코드를 구현할 수 있으며, 런타입에 발생하는 에러를 줄일 수 있다.

하지만 자바스크립트는 변수를 선언할 때 타입을 선언하지 않는다. var, let const 키워드만 사용할 뿐이다. 자바스크립트의 변수에는 어떤 타입의 값이이라도 할당이 가능하다.

var foo;
console.log(typeof foo); // undefined;

foo = 3;
console.log(typeof foo); // number

foo = 'hello';
console.log(typeof foo); // string

foo = true;
console.log(typeof foo); // booean

foo = null;
console.log(typeof foo); // object

foo = Symbol();
console.log(typeof foo); // symbol

foo = {};
console.log(typeof foo); // object

foo = [];
console.log(typeof foo); //object

foo = function () {};
console.log(typeof foo); // function

typeof 연산자는 변수의 데이터 타입이 아닌 변수에 할당된 값의 데이터 타입을 반환한다.

자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정된다. 그리고 재할당에 의해 변수의 타입이 언제든지 동적으로 변할 수 있다. 이러한 특징을 동적 타이핑이라고 한다.

동적 타입 언어와 변수

동적 타입 언어는 변수에 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있다. 이는 매우 편리하지만 이면에는 위험도 도사리고 있다.
복잡한 프로그램 안에서 변수의 값은 빈번히 변하게 되고, 동적 타입 언어는 변수에 어떤 값이 할당 되냐에 따라서 데이터 타입이 변하기 까지 한다. 그러므로 동적 타입 언어는 변수의 값을 확인하기 전에는 타입을 확신할 수 없다.

또한 자바스크립트는 개발자의 의도와 상관없이 자바스크립트 엔진의 암묵적인 룰에 의해서 타입이 자동으로 변하기도 한다. 숫자 타입이라고 예측하고 프로그램을 짯지만 알고보니 문자 타입의 변수일 수도 있다.

결국 이러한 이유로 변수를 사용하기 전에 매번 데이터 타입을 체크하게 되는데 이는 번거로울 뿐아니라 코드의 양도 증가하고 지저분해진다.

따라서 변수를 사용할 때 아래의 주의점을 참고하자.

  • 변수는 꼭 필요한 경우에 한해 제한적으로 사용한다.
  • 변수의 스코프를 최대한 좁게 만들어야 한다.
  • 전역 변수는 최대한 사용하지 않는다.
  • 변수보다는 최대한 상수를 사용한다.
  • 의미있는 변수 네이밍이 필요하다.

0개의 댓글