5. 데이터 타입

1. 데이터 타입이란 무엇인가? 왜 필요한가?

데이터 타입(Data Type. 줄여서 타입이라고도 부른다)은 값의 종류를 말한다. 자바스크립트의 모든 값은 데이터 타입을 갖는다. 데이터 타입이란 무엇인지 그리고 왜 필요한지 살펴보도록 하자.

1.1. 데이터 타입에 의한 메모리 공간의 확보

프로그래밍 언어에서 사용할 수 있는 모든 값은 메모리에 저장하고 참조할 수 있어야 한다. 메모리에 값을 저장하기 위해서는 먼저 확보해야 할 메모리 공간의 크기를 알아야한다. 이는 값의 종류에 따라 확보해야 할 메모리의 크기가 다르기 때문이다. 다시 말해 몇 byte의 메모리 공간을 사용해야 손실없이 값을 저장할 수 있는지 알아야 한다는 의미이다.

예를 들어 아래와 같이 변수를 선언하고 숫자 값을 할당해 보자.

var score = 100;

위 코드가 실행되면 컴퓨터는 숫자 값 100을 저장하기 위해 메모리 공간을 확보한 다음, 확보된 메모리에 숫자 값 100을 2진수로 저장한다. 이러한 처리를 수행하려면 숫자 값을 저장할 때 확보해야 할 메모리 공간의 크기를 알아야 한다. 자바스크립트 엔진은 데이터 타입, 즉 값의 종류에 따라 적절한 크기의 메모리 공간을 확보한다.

위 예제의 경우, 자바스크립트 엔진은 값 100을 숫자 타입이라는 데이터 타입으로 인식하고 8byte의 메모리 공간을 확보한다. 그리고 10진수 100을 2진수로 저장한다.

숫자 타입 값의 할당

ECMAScript 사양은 데이터 타입의 크기를 명시적으로 규정하고 있지는 않다. 데이터 타입에 따라 확보되는 메모리 공간의 크기는 브라우저 제조사의 구현에 따라 다를 수 있다.

자바스크립트는 숫자 타입의 값을 2진수로 만들 때 배정밀도 64비트 부동소수점 포맷(double-precision 64-bit floating-point format)을 사용한다. 따라서 실제로 저장되는 2진수 값은 위 그림과 다르다. 지금은 간단히 양의 정수로 저장된다고 생각하자.

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

이번에는 값을 참조하는 경우를 생각해보자. 식별자 score를 통해 숫자 타입의 값 100이 저장되어 있는 메모리 공간의 주소를 찾아갈 수 있다. 정확히 말하면 값 100이 저장되어 있는 메모리 공간의 선두 메모리 셀의 주소를 찾아갈 수 있다.

이때 컴퓨터는 한번에 읽어 들여야 할 메모리 공간의 크기, 즉 메모리 셀의 개수(byte 수)를 알아야 한다. 변수 score의 경우, 저장되어 있는 값이 숫자 타입이므로 8byte 단위로 읽어 들이지 않으면 값이 훼손된다. 그렇다면 컴퓨터는 한번에 읽어 들여야 할 메모리 셀의 크기를 어떻게 알 수 있는 것일까? 변수 score에는 숫자 타입의 값이 할당되어 있으므로 자바스크립트 엔진은 변수 score를 숫자 타입으로 인식한다. 다시 말해 숫자 타입의 변수는 8byte 단위로 메모리 공간에 저장된 값을 읽어 들여야 한다는 것을 알게 된다.

그런데 아직 문제가 남아 있다. 메모리에서 읽어 들인 2진수를 어떻게 해석해야 하는지에 대한 것이다. 컴퓨터는 모든 데이터를 2진수로 처리한다. 숫자, 텍스트, 이미지, 동영상 등의 모든 데이터를 2진수로 처리한다. 예를 들어 2진수 0100 0001를 숫자로 해석하면 65이지만 문자로 해석하면 ‘A’이다.

// binary => decimal
console.log(parseInt('01000001', 2)); // 65
// binary => Unicode
console.log(String.fromCharCode(parseInt('01000001', 2))); // A

위에서 설명한 바와 같이 변수 score는 숫자 타입이다. 따라서 컴퓨터는 변수 score가 가리키는 메모리 공간의 주소에서 읽어 들인 2진수를 숫자로 해석한다.

지금까지 살펴본 데이터 타입에 대해 정리해보자.

데이터 타입(Data Type)은 값의 종류를 말한다. 자바스크립트의 모든 값은 데이터 타입을 갖는다. 데이터 타입이 필요한 이유는 아래와 같다.

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

2. 값

지금까지 살펴 본 내용 중에 “값”이라는 용어가 자주 등장했다. 값을 비롯해 이 책에 등장하는 대부분의 용어는 자바스크립트만의 전유물이 아닌 컴퓨터 공학 전반에서 사용하는 용어다.

용어에 대한 정확한 이해는 개념을 정립하는데 빠질 수 없는 필수 요소로서 정확한 커뮤니케이션을 가능케 한다. 개발자 간의 의사소통 뿐만이 아니라 서적이나 매뉴얼과 같은 문서의 이해를 도우며 프로그래밍 언어를 학습하는 데 있어 중요한 역할을 한다. 앞으로 등장할 용어에 대해서도 주의 깊게 살펴보도록 하자.

값(value)은 더 이상 평가할 수 없는 하나의 표현식이다. 표현식은 값을 생성하는 문이다. 즉, 표현식은 평가되어 값을 생성한다. 아래 예제를 살펴보자.

// 10 + 20은 표현식이다. 이 표현식은 평가되어 30이라는 값을 만든다.
10 + 20

10 + 20은 표현식이다. 이 표현식은 값인 10과 20 그리고 연산자로 구성되어 있다. 이 표현식은 자바스크립트 엔진에 의해 평가(evaluation)되어 30이라는 새로운 값을 생성한다. 새롭게 생성된 값 30은 더 이상 평가할 수 없다. 다시 말해, 30을 평가하면 언제나 30이다.

값은 데이터 타입을 갖으며 메모리에 2진수, 즉 비트(bit)의 나열로 저장된다. 데이터 타입은 값의 종류를 말한다. 즉, 값에는 종류가 있다. 표현식 10 + 20이 평가되어 생성한 값 30의 데이터 타입은 숫자 타입이다. 이 숫자 타입 30은 메모리에 2진수로 저장된다.

변수(Variable)는 하나 값을 저장할 수 있는 메모리 공간에 붙인 이름 또는 메모리 공간 자체를 말한다고 했다. 따라서 값은 변수에 할당할 수 있다.

// 변수에는 표현식 10 + 20의 평가되어 생성한 값 30이 할당된다.
var sum = 10 + 20;

위 예제의 변수 sum에 할당되는 것은 표현식이 아니라 표현식이 평가되어 생성한 값 30이다.

3. 값의 생성

값은 다양한 방법(표현식)으로 생성할 수 있다. 가장 기본적인 방법은 리터럴 표기법을 사용하는 것이다.

3.1. 리터럴

리터럴(literal)은 소스 코드 안에서 직접 만들어 낸 고정된 값 자체를 말한다. 좀 더 명확히 말하면 리터럴은 자바스크립트 엔진에 의해 해석되어 값으로 평가된다. 리터럴은 리터럴 표기법(Literal notation)으로 생성한다.

3.1.1 리터럴 표기법을 통한 값의 생성

리터럴을 기술하는 방식인 리터럴 표기법(Literal notation)은 값을 생성하는 방법이다. 아래 예제를 살펴보자.

// 리터럴 표기법으로 숫자 리터럴 3을 기술하였다.
// 자바스크립트 엔진은 런타임에 숫자 리터럴 3을 해석하여 숫자 타입의 값 3을 생성한다.
3

리터럴 표기법으로 숫자 리터럴 3을 기술하였다. 리터럴은 사람이 이해할 수 있는 표기법으로 값의 생성을 자바스크립트 엔진에게 명령하는 것이다. 위 예제의 경우, 사람이 이해할 수 있는 표기법인 아라비아 숫자 표기법으로 자바스크립트 엔진에게 숫자 값의 생성을 명령한 것이다.

리터럴 표기법으로 기술한 리터럴은 자바스크립트 엔진에 의해 해석되어 값으로 평가된다. 다시 말해, 자바스크립트 엔진은 리터럴 표기법으로 작성된 코드(리터럴)를 만나면, 코드가 실행되는 시점(런타임, runtime)에 리터럴을 해석하고 리터럴에 상응하는 값을 생성한다. 따라서 리터럴은 결국 값이 되므로 리터럴을 값 자체라고 표현할 때가 많다.

리터럴은 그 자체로 표현식이며 표현식의 일부로서 다른 값을 생성하는데 사용되기도 한다.

// 리터럴 10과 리터럴 20은 표현식의 일부이다.
10 + 20

리터럴 표기법을 사용하면 아래와 같이 자바스크립트에서 사용할 수 있는 다양한 타입의 값(숫자, 문자열, 불리언, null, undefined, 객체, 배열, 함수, 정규 표현식 등)을 생성할 수 있다.

// 정수 리터럴
100
// 부동 소숫점 리터럴
10.5
// 2진수 리터럴(0b로 시작)
0b01000001
// 8진수 리터럴(ES6에서 도입. 0o로 시작)
0o101
// 16진수 리터럴(ES6에서 도입. 0x로 시작)
0x41

// 문자열 리터럴
'Hello'
"World"

// 불리언 리터럴
true
false

// null 리터럴
null

// undefined 리터럴
undefined

// 객체 리터럴
{ name: 'Lee', gender: 'male' }

// 배열 리터럴
[ 1, 2, 3 ]

// 함수 리터럴
function() {}

// 정규표현식 리터럴
/ab+c/

리터럴 표기법은 자바스크립트 엔진과 개발자 간의 약속으로 이해할 수 있다. 자바스크립트 엔진은 리터럴 표기법을 해석할 수 있다. 예를 들어 숫자 값을 생성하고 싶으면 개발자는 숫자 리터럴 표기법을 사용해 숫자 값의 생성을 자바스크립트 엔진에게 요청한다. 자바스크립트 엔진은 숫자 리터럴을 해석하여 숫자 값을 생성하라는 개발자의 요청에 응답한다.

리터럴 표기법은 값을 생성하는 방법이라고 했다. 아래의 예제를 살펴보자.

// 정수 리터럴
65
// 2진수 리터럴
0b01000001
// 8진수 리터럴
0o101
// 16진수 리터럴
0x41

위 예제의 리터럴 들은 표기법만 다를 뿐 동일한 값을 생성한다.

// 표기법만 다를 뿐 같은 값이다.
console.log(0b01000001 === 65);    // true
console.log(0b01000001 === 0o101); // true
console.log(0b01000001 === 0x41);  // true

3.1.2 값과 리터럴의 관계

아래 예제를 살펴보자.

var score = 100;

우변의 100은 리터럴이다. 리터럴은 자바스크립트 엔진에 의해 값으로 평가되므로 결국 리터럴은 값이라고 할 수 있다. 100은 리터럴이자 값이므로 변수에 할당할 수 있다.

다른 예제를 살펴보자.

var score = 50 + 50;

우변을 살펴보면 두개의 리터럴 50을 + 연산자를 사용해 합산하고 있다. 이 식은 새로운 값 100을 생성한다. 이처럼 프로그램 내에서 값을 생성하는 문을 표현식(expression)이라 한다. 위 코드의 우변은 표현식이며 새로운 값 100을 생성한다. 이처럼 리터럴은 값을 생성하는 표현식의 구성 요소가 될 수 있다.

리터럴은 그 자체로 값이 될 수 있지만 값이 리터럴인 것은 아니다. 리터럴 100은 자바스크립트 엔진에 의해 평가되어 숫자값 100이 되지만 숫자값 100이 리터럴은 아니다.

3.2. 표현식

값은 다양한 방법으로 생성할 수 있다고 했다. 다양한 방법이란 바로 표현식을 말한다.

표현식(expression)은 리터럴, 식별자(변수명, 함수명 등), 연산자, 함수 호출 등의 조합을 말한다. 표현식은 평가(evaluation. 표현식을 해석하여 하나의 값을 만드는 과정)되어 하나의 값을 만든다.

즉, 표현식은 하나의 값으로 평가될 수 있는 문(statement)이다. 아래와 같이 다양한 표현식이 있지만 하나의 값으로 평가된다는 점에서 동일하다.

// 리터럴 표현식
10
'Hello'

// 식별자 표현식(선언이 이미 존재한다고 가정)
sum
person.name
arr[1]

// 연산자 표현식
10 + 20
sum = 10
sum !== 10

// 함수/메소드 호출 표현식(선언이 이미 존재한다고 가정)
square()
person.getName()

표현식에 대해서는 “6.1 표현식과 연산자”에서 좀 더 자세히 살펴볼 것이다. 지금은 표현식이 값을 만드는 방법이라는 점에 주목하도록 하자.

4. 데이터 타입의 분류

자바스크립트의 모든 값은 데이터 타입을 갖는다. 자바스크립트는 어떠한 데이터 타입을 제공하며 그 특징은 무엇인지 살펴보도록 하자.

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

  • 원시 타입(primitive type)
    • 숫자(number) 타입: 숫자 (정수, 실수)
    • 문자열(string) 타입: 문자열
    • 불리언(boolean) 타입: 논리적 참(true)과 거짓(false)
    • undefined 타입: 선언은 되었지만 값을 할당하지 않은 변수에 암묵적으로 할당되는 값
    • null 타입: 값이 없다는 것을 의도적으로 명시할 때 사용하는 값
    • Symbol 타입: ES6에서 새롭게 추가된 7번째 타입
  • 객체 타입 (object/reference type): 객체, 함수, 배열 등

예를 들어 숫자(number) 타입의 값 1과 문자열(string) 타입의 값 ‘1’은 비슷하게 보이지만 전혀 다른 값이다. 확보해야 할 메모리 공간의 크기도 다르고 메모리에 저장되는 2진수도 다르며 읽어 들여 해석하는 방식도 다르다.

물론 값을 생성한 목적과 용도도 다르다. 숫자 타입의 값은 주로 산술 연산을 위해 만들지만 문자열 타입의 값은 주로 텍스트를 화면에 출력하기 위해 만든다. 이처럼 개발자는 명확한 의도를 가지고 타입을 구별하여 값을 만들 것이고 자바스크립트 엔진은 타입을 구별하여 값을 취급할 것이다.

5. 숫자 타입

C나 Java의 경우, 정수와 실수를 구분하여 int, long, float, double 등과 같은 다양한 숫자 타입이 존재한다. 하지만 자바스크립트는 독특하게 하나의 숫자 타입만 존재한다.

ECMAScript 사양에 따르면 숫자 타입의 값은 배정밀도 64비트 부동소수점 형식(double-precision 64-bit floating-point format : -(253 -1) ~ 253 -1 사이의 숫자 값)을 따른다. 즉, 모든 수를 실수로 처리하며 정수만을 표현하기 위한 특별한 데이터 타입(integer type)은 없다.

// 모두 숫자 타입이다.
var integer = 10;    // 정수
var double = 10.12;  // 실수
var negative = -20;  // 음의 정수

정수, 실수, 2진수, 8진수, 16진수 리터럴은 모두 메모리에 배정밀도 64비트 부동소수점 형식의 2진수로 저장된다. 자바스크립트는 2진수, 8진수, 16진수를 표현하기 위한 데이터 타입을 제공하지 않기 때문에 이들 값을 참조하면 모두 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

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

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

자바스크립트는 대소문자를 구별(case-sensitive)하므로 NaN을 NAN, Nan, nan과 같이 표현하면 에러가 발생하니 주의하기 바란다.

// 자바스크립트는 대소문자를 구별한다.
var x = nan; // ReferenceError: nan is not defined

6. 문자열 타입

문자열(String) 타입은 텍스트 데이터를 나타내는데 사용한다. 문자열은 0개 이상의 16bit 유니코드 문자(UTF-16) 들의 집합으로 전세계 대부분의 문자를 표현할 수 있다.

문자열은 작은 따옴표(‘’), 큰 따옴표(“”) 또는 백틱(``) 안에 텍스트를 넣어 생성한다. 가장 일반적인 표기법은 작은 따옴표를 사용하는 것이다.

// 문자열 타입
var string;
string = "문자열"; // 큰 따옴표
string = '문자열'; // 작은 따옴표
string = `문자열`; // 백틱 (ES6)

string = "큰 따옴표로 감싼 문자열 내의 '작은 따옴표'는 문자열로 인식된다.";
string = '작은 따옴표로 감싼 문자열 내의 "큰 따옴표"는 문자열로 인식된다.';

C나 Java와 같은 언어와는 다르게 자바스크립트의 문자열은 원시 타입이며 변경 불가능한 값 (immutable value)다. 이것은 한 번 문자열이 생성되면, 그 문자열을 변경할 수 없다는 것을 의미한다. 이에 대해서는 “10.1.2. 문자열과 불변성”에서 살펴보기로 하자.

6.1. 템플릿 리터럴

ES6부터 템플릿 리터럴(Template literal)이라고 불리는 새로운 문자열 표기법이 도입되었다. 템플릿 리터럴은 일반 문자열과 비슷해 보이지만, ‘ 또는 “ 같은 통상적인 따옴표 문자 대신 백틱(backtick) 문자 `를 사용한다.

const template = `템플릿 리터럴은 '작은따옴표(single quotes)'과 "큰따옴표(double quotes)"를 혼용할 수 있다.`;

console.log(template);
// 템플릿 리터럴은 '작은따옴표(single quotes)'과 "큰따옴표(double quotes)"를 혼용할 수 있다.

일반적인 문자열에서 줄바꿈은 허용되지 않으며 공백(white space)를 표현하기 위해서는 백슬래시(\)로 시작하는 이스케이프 시퀀스(Escape Sequence)를 사용하여야 한다.

ES6 템플릿 리터럴은 일반적인 문자열과 달리 여러 줄에 걸쳐 문자열을 작성할 수 있으며 템플릿 리터럴 내의 모든 공백은 있는 그대로 적용된다.

const template = `<ul class="nav-items">
  <li><a href="#home">Home</a></li>
  <li><a href="#news">News</a></li>
  <li><a href="#contact">Contact</a></li>
  <li><a href="#about">About</a></li>
</ul>`;

console.log(template);
/*
<ul class="nav-items">
  <li><a href="#home">Home</a></li>
  <li><a href="#news">News</a></li>
  <li><a href="#contact">Contact</a></li>
  <li><a href="#about">About</a></li>
</ul>
*/

문자열은 + 문자열 연산자를 사용해 연결할 수 있다.

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

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

템플릿 리터럴은 + 문자열 연산자를 사용하지 않아도 간단한 방법으로 새로운 문자열을 삽입할 수 있는 기능을 제공한다. 이를 문자열 인터폴레이션(String Interpolation)이라 한다.

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

// ES6: String Interpolation
console.log(`My name is ${first} ${last}.`);
// My name is Ung-mo Lee.

문자열 인터폴레이션은 ${ expression }으로 표현식을 감싼다. 이때 표현식의 평가 결과는 문자열로 강제 타입 변환된다.

console.log(`1 + 1 = ${1 + 1}`); // 1 + 1 = 2

7. 불리언 타입

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

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

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

불리언 타입의 값은 참과 거짓으로 구분되는 조건에 의해 프로그램의 흐름을 제어하는 조건문에서 자주 사용한다. 이에 대해서는 “7.2. 조건문”에서 살펴보기로 하자.

8. undefined 타입

undefined 타입의 값은 undefined가 유일하다. 선언 이후 명시적으로 값을 할당하지 않은 변수는 자바스크립트 엔진의 암묵적 초기화에 의해 undefined 값을 가진다. 따라서 선언은 되었지만 아직 값을 할당하지 않은 변수에 접근하면 undefined가 반환된다.

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

이는 변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이루어질 때까지 빈 상태(대부분 비어있지 않고 쓰레기 값(Garbage value)이 들어 있다)로 내버려두지 않고 자바스크립트 엔진이 undefined로 초기화하기 때문이다.

이처럼 undefined는 개발자가 의도적으로 할당하기 위한 값이 아니라 자바스크립트 엔진이 변수를 초기화할 때 사용하는 값이다. 변수를 참조했을 때 undefined가 반환된다면 참조한 변수가 선언 이후 값이 할당된 적인 없는 변수라는 것을 개발자는 간파할 수 있다.

자바스크립트 엔진이 변수 초기화에 사용하는 undefined를 개발자가 의도적으로 변수에 할당한다면 undefined의 본래의 취지와 어긋날 뿐더러 혼란을 줄 수 있으므로 권장하지 않는다.

그렇다면 변수에 값이 없다는 것을 명시하고 싶은 경우 어떻게 하면 좋을까? 그런 경우는 undefined를 할당하는 것이 아니라 null을 할당한다.

선언(Declaration)과 정의(Definition)
undefined를 한국어로 직역하면 “정의되지 않은”이다. 여기서 말하는 정의란 변수에 값을 할당하여 변수의 실체를 명확히 하는 것을 말한다.
다른 프로그래밍 언어에서는 선언과 정의를 엄격하게 구분하여 사용하는 경우가 있다. C에서 선언은 식별자의 존재를 알리는 것이고 정의는 식별자에 값을 할당하여 식별자의 실체를 명확히 하는 것이다.
자바스크립트의 변수는 선언과 동시에 undefined로 정의되므로 선언과 정의의 구분이 모호하다. 따라서 자바스크립트에서는 선언과 정의를 혼용해서 사용하는 경향이 있다.
var a = 1; // 변수 a를 선언하고 a는 1이라고 정의
var b;     // 변수 b를 선언. 하지만 내부적으로 b는 undefined라고 정의된다.
b = 1;     // 변수 b는 1이라고 정의

9. null 타입

null 타입의 값은 null이 유일하다. 자바스크립트는 대소문자를 구별(case-sensitive)하므로 null은 Null, NULL등과 다르다.

프로그래밍 언어에서 null은 변수에 값이 없다는 것을 의도적으로 명시(의도적 부재 Intentional absence)할 때 사용한다.

(그다지 유용하지도 않으며 잘 사용하지도 않는 표현이지만) 변수에 null을 할당하는 것은 변수가 이전에 참조하던 값을 더이상 참조하지 않겠다는 의미이다. 이는 이전에 할당되어 있던 값에 대한 참조를 명시적으로 제거하는 것을 의미하며 자바스크립트 엔진은 누구도 참조하지 않는 메모리 공간에 대해 가비지 콜렉션을 수행할 것이다.

var foo = 'Lee';

// 이전에 할당되어 있던 값에 대한 참조를 제거. 변수 foo는 더이상 'Lee'를 참조하지 않는다.
// 유용해 보이지는 않는다. 변수의 스코프를 좁게 만들어 변수 자체를 재빨리 소멸시키는 편이 낳다.
foo = null;

함수가 유효한 값을 반환할 수 없는 경우, 명시적으로 null을 반환하기도 한다. 예를 들어, 조건에 부합하는 HTML 요소를 검색해 반환하는 Document.querySelector 메소드는 조건에 부합하는 HTML 요소를 검색할 수 없는 경우, 에러 대신 null을 반환한다.

<!DOCTYPE html>
<html>
<body>
  <script>
    var element = document.querySelector('.myElem');

    // HTML 문서에 myElem 클래스를 갖는 요소가 없다면 null을 반환한다.
    console.log(element); // null
  </script>
</body>
</html>

10. symbol 타입

심볼(symbol)은 ES6에서 새롭게 추가된 7번째 타입으로 변경 불가능한 원시 타입의 값이다. 심볼은 주로 이름의 충돌 위험이 없는 객체의 유일한 프로퍼티 키(property key)를 만들기 위해 사용한다.

심볼은 Symbol 함수를 호출해 생성한다. 이때 생성된 심볼 값은 다른 심볼 값들과 다른 유일한 심볼 값이다. 심볼에 대해서는 객체를 학습한 이후, 좀 더 자세히 살펴보도록 하자.

// 심볼 값 생성
var key = Symbol('key');
console.log(typeof key); // symbol

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

// 심볼 key를 이름의 충돌 위험이 없는 유일한 프로퍼티 키로 사용한다.
obj[key] = 'value';
console.log(obj[key]); // value

11. 객체 타입

자바스크립트의 데이터 타입은 원시 타입과 객체 타입으로 크게 분류한다고 했다. 그 이유는 무엇일까? 원시 타입과 객체 타입은 근본적으로 다르다는 의미일 것이다. 이 의문에 대한 설명은 아직 객체에 대해 살펴보지 않았으므로 잠시 미루도록 하자.

중요한 것은 자바스크립트는 객체 기반의 언어이며 자바스크립트를 이루고 있는 거의 “모든 것”이 객체라는 것이다. 지금까지 살펴본 6가지의 데이터 타입 이외의 값은 모두 객체 타입이다.

12. 동적 타이핑

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

자바스크립트의 모든 값은 데이터 타입을 갖는다고 했다. 그렇다면 변수는 데이터 타입을 갖을까?

C나 Java와 같은 정적 타입(Static/Strong type) 언어는 변수를 선언할 때 변수에 할당할 수 있는 값의 종류, 즉 데이터 타입을 사전에 선언해야 한다. 이를 명시적 타입 선언(explicit type declaration)이라 한다. 다음은 C에서 정수 타입의 변수를 선언하는 예이다.

// 변수 c에는 1byte 정수 타입의 값(-128 ~ 127)만을 할당할 수 있다.
char c;

// 변수 num에는 4byte 정수 타입의 값(-2,124,483,648 ~ 2,124,483,647)만을 할당할 수 있다.
int num;

정적 타입 언어는 변수의 타입을 변경할 수 없으며 변수에 선언한 타입에 맞는 값만을 할당할 수 있다. 정적 타입 언어는 컴파일 시점에 타입 체크(선언한 데이터 타입에 맞는 값을 할당했는지 검사하는 처리)를 수행한다. 만약 타입 체크를 통과하지 못했다면 에러를 발생시키고 프로그램의 실행 자체를 막는다. 이를 통해 타입의 일관성을 강제하여 보다 안정적인 코드의 구현을 통해 런타임에 발생하는 에러를 줄인다. 대표적인 정적 타입 언어는 C, C++, Java, Kotlin, Go, Haskell, Rust, Scala 등이 있다.

자바스크립트는 정적 타입 언어와는 다르게 변수를 선언할 때 타입을 선언하지 않는다. 다만 var, let, const 키워드를 사용해 변수를 선언할 뿐이다. 자바스크립트의 변수는 정적 타입 언어와 같이 미리 선언한 데이터 타입의 값만을 할당할 수 있는 것이 아니다. 어떠한 데이터 타입의 값이라도 자유롭게 할당할 수 있다.

하나의 변수를 선언하고 지금까지 살펴본 다양한 데이터 타입의 값을 할당한 다음, typeof 연산자로 변수의 데이터 타입을 조사해 보자. typeof 연산자(“6.12 typeof 연산자” 참고)는 자신의 뒤에 위치한 피연산자의 데이터 타입을 문자열로 반환한다.

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);  // boolean

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 연산자로 변수를 연산해 보면 변수의 데이터 타입을 반환한다. 정확히 말하면 변수의 데이터 타입을 반환하는 것이 아니라 변수에 할당된 값의 데이터 타입을 반환한 것이다.

자바스크립트의 변수는 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있으므로 정적 타입 언어에서 말하는 데이터 타입과 개념이 다르다. 정적 타입 언어는 변수 선언 시점에 변수의 타입이 결정되고 변수의 타입을 변경할 수 없다. 자바스크립트는 값을 할당하는 시점에 변수의 타입이 동적으로 결정되고 변수의 타입을 언제든지 자유롭게 변경할 수 있다.

다시 말해 자바스크립트 변수는 선언이 아닌 할당에 의해 타입이 결정된다. 그리고 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다. 이러한 특징을 동적 타이핑(Dynamic typing)이라 하며 자바스크립트를 정적 타입 언어와 구별하기 위해 동적 타입(Dynamic/Weak type) 언어라 부른다. 대표적인 동적 타입 언어는 자바스크립트, Python, PHP, Ruby, Lisp, Perl 등이 있다.

12.2. 동적 타입 언어와 변수

동적 타입 언어는 변수에 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있다. 이러한 동적 타입 언어의 특징은 데이터 타입에 대해 무감각해질 정도로 편리하다. 하지만 언제나 그렇듯 편리함의 이면에는 위험도 도사리고 있다.

모든 소프트웨어 아키텍처에는 트레이드오프(trade-off)가 존재하며 모든 애플리케이션에 적합한 은 탄환(Silver bullet)은 없듯이, 동적 타입 언어 또한 구조적인 단점이 있다.

트레이드오프(trade-off)
두 개의 정책 목표 가운데 하나를 달성하려고 하면 다른 목표의 달성이 늦어지거나 희생되는 모순적 관계를 의미한다. 예를 들어, 실업률을 줄이면 물가가 상승하고, 물가를 안정시키면 실업률이 높아진다.
은 탄환(Silver bullet)
고질적인 문제를 단번에 해결할 수 있는 명쾌한 해결책

복잡한 프로그램에서는 동적으로 변화하는 데이터 타입을 추적하기 어려울 수 있다. 변수의 값은 언제든지 의도치 않게 변경될 수 있다. 뿐만 아니라, 변수의 데이터 타입이 고정되어 있지 않고 동적으로 변하는 동적 타입 언어는 변수가 저장하고 있는 값을 확인하기 전에는 값의 타입을 확신할 수 없다.

더욱이 자바스크립트는 개발자의 의도와는 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되기도 한다. 즉, 숫자 타입의 변수일 것이라고 예측했지만 사실은 문자열 타입의 변수일 수도 있다는 말이다. 예측에 의해 작성된 프로그램은 당연히 오류를 뿜어낼 것이다. 결국 동적 타입 언어는 유연성(flexibility)은 높지만, 신뢰성(reliability)은 떨어진다.

이러한 이유로 안정적인 프로그램을 만들기 위해 변수를 사용하기 이전에 데이터 타입을 체크해야 하는 경우가 있는데 이는 매우 번거로울 뿐만 아니라 코드량도 증가한다. 코드량이 증가하면 버그가 발생할 확률도 높아지며 테스트 분량도 증가한다. 따라서 변수를 사용할 때 주의할 사항은 아래와 같다.

  • 변수의 사용을 적극적으로 줄인다. 변수의 개수가 많으면 많을수록 오류가 발생할 확률은 높아진다.
  • 전역 변수는 사용하지 않는다. 변수의 생명주기를 최대한 짧게 만든다.
  • 변수보다는 상수를 사용해 값의 변경을 억제한다.
  • 변수명은 변수의 존재 이유를 파악할 수 있도록 명명한다.

이에 대해서는 해당하는 내용이 나올 때마다 언급하도록 하겠다.

Back to top