0%

제어문

제어문은 조건에 따라 코드 블록을 실행(조건문)하거나 반복 실행(반복문)할 때 사용한다.
일반적으로 코드는 위에서 아래 방향으로 순차적으로 실행된다.
제어문을 사용하면 코드의 실행 흐름을 인위적으로 제어할 수 있다.

하지만 코드의 실행 순서가 변경되는 것은 코드의 흐름을 혼란스럽게 만들어 가독성을 해치는 단점이 있다.
가독성이 좋지 않은 코드는 오류를 발생시키는 원인이 되므로 forEach, map, filter, reduce 같은 고차 함수를 사용한 함수형 프로그래밍 기법에서는 제어문의 사용을 억제하여 복잡성을 해결하려고 노력한다.

하지만 제어문을 바르게 이해하는 것은 코딩 스킬에 많은 영향을 준다.
특히 for 문은 매우 중요하므로 확실히 살펴보도록 하자.

블록문

블록문은 0개 이상의 문을 중괄호로 묶은 것으로, 코드 블록 또는 블록이라고 부르기도 한다.
자바스크립트는 블록문을 하나의 실행 단위로 취급한다.
블록문은 단독으로 사용 가능하나 일반적으로 제어문이나 함수를 정의할 때 사용한다.

문의 끝에는 세미콜론을 붙이는 것이 일반적이나 블록문은 자체 종결성을 갖기 때문에 블록문의 끝에는 세미콜론을 붙이지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 블록문
{
var foo = 10;
}

// 제어문
var x = 1;
if (x < 10) {
x++;
}

// 함수 선언문
function sum(a, b) {
return a + b;
}

조건문

조건문은 주어진 조건식의 평가 결과에 따라 코드 블록의 실행을 결정한다.
조건식은 불리언 값으로 평가될 수 있는 표현식이다.

자바스크립트는 if…else 문과 switch 문의 두 가지 조건문을 제공한다.

if…else 문

if…else 문은 주어진 조건식의 평가 결과에 따라 실행할 코드 블록을 결정한다.
조건식의 평가 결과가 true일 경우 if 문의 코드 블록이 실행되고 false일 경우 else 문의 코드 블록이 실행된다.

if (조건식) {
  // 조건식이 참이면 이 코드 블록이 실행
} else {
  // 조건식이 거짓이면 이 코드 블록이 실행
}

if 문의 조건식은 불리언 값으로 평가되어야 한다. 만약 if 문의 조건식이 불리언 값이 아닌 값으로 평가되면 자바스크립트 엔진에 의해 암묵적으로 불리언 값으로 강제 변환되어 실행할 코드 블록을 결정한다.

만약 코드 블록 내의 문이 하나라면 중괄호를 생략할 수 있다.

1
2
3
4
5
6
7
8
var num = 2;
var kind;

if (num > 0) kind = '양수';
else if (num < 0) kind = '음수';
else kind = '영';

console.log(kind); // 양수

대부분의 if…else 문은 삼항 조건 연산자로 바꿔 쓸 수 있다.
연산자에서도 살펴보았듯이 조건에 따라 단순히 값을 결정하여 변수에 할당하는 경우 if…else 문보다 삼항 조건 연산자를 사용하는 편이 가독성이 좋다.
하지만 조건에 따라 실행해야 할 내용이 복잡하여 여러 줄의 문이 필요하다면 if…else 문을 사용하는 편이 가독성이 좋다.

switch 문

switch 문은 주어진 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case 문으로 실행 흐름을 옮긴다.
case 문은 상황을 의미하는 표현식을 지정하고 콜론으로 마친다. 그리고 그 뒤에 실행할 문들을 위치시킨다.

switch 문의 표현식과 일치하는 case 문이 없다면 실행 순서는 default 문으로 이동한다. default 문은 선택사항이다.

switch (표현식) {
 case 표현식1:
  switch 문의 표현식과 표현식 1이 일치하면 실행될 문;
  break;
 case 표현식2:
  switch 문의 표현식과 표현식 2가 일치하면 실행될 문;
  break;
 default:
  switch 문의 표현식과 일치하는 case 문이 없을 때 실행될 문;
}

if…else 문의 조건식은 불리언 값으로 평가되어야 하지만 switch 문의 표현식은 불리언 값보다는 문자열이나 숫자 값인 경우가 많다.
즉 switch 문은 논리적 참, 거짓보다는 다양한 상황(case)에 따라 실행할 코드 블록을 결정할 때 사용한다.

swtich 문은 case, default, break 등 다양한 키워드를 사용해야 하고 폴스루가 발생한느 등 문법도 복잡하다.
따라서 C 언어를 기반으로 하는 프로그래밍 언어는 대부분 switch 문을 지원하지만 파이썬과 같이 switch 문ㅇ을 지원하지 않는 프로그래밍 언어도 있다.

만약 if…else 문으로 해결할 수 있다면 switch 문보다 if…else 문을 사용하는 편이 좋다.
하지만 조건이 너무 많아 if…else 문보다 switch 문을 사용했을 때 가독성이 더 좋다면 switch문을 사용하는 편이 좋다.

반복문

반복문은 조건식의 평가 결과가 참인 경우 코드 블록을 실행한다. 그 후 조건식을 다시 평가하여 여전히 참인 경우 코드 블록을 다시 실행한다.
이는 조건식이 거짓일 때까지 반복된다.

자바스크립트는 for 문, while 문, do…while 문의 세 가지 반복문을 제공한다

for 문

for 문은 조건식이 거짓으로 평가될 때까지 코드 블록을 반복 실행한다. 가장 일반적으로 사용되는 for 문의 형태는 다음과 같다.

for (변수 선언문 또는 할당문; 조건식; 증감식) {
 조건식이 참인 경우 반복 실행될 문;
}

for 문은 매우 중요하므로 어떻게 동작하는지 많은 연습을 통해 확실히 이해해야 한다.

for 문의 변수 선언문, 조건식, 증감식은 모두 옵션이므로 반드시 사용할 필요는 없다. 단, 어떤 식도 선언하지 않으면 무한루프가 된다.

1
for (;;) {...} // 무한루프

for 문 내에 for 문을 중첩해 사용할 수 있다. 이를 중첩 for 문이라 한다. 다음은 두 개의 주사위를 던졌을 때 두 눈의 합이 6이 되는 모든 경우의 수를 출력하기 위해 이중 중첩 for 문을 사용한 예다.

1
2
3
4
5
6
for (var i = 1; i <= 6; i++) {
for (var j = 1; j <= 6; j++) {
if (i + j === 6) console.log(`[${i}, ${j}]`);
}
}
// [1, 5] [2, 4] [3, 3] [4, 2] [5, 1]

while 문

while 문은 주어진 조건식의 평가 결과가 참이면 코드 블록을 계속해서 반복 실행한다.
for 문은 반복 횟수가 명확할 때 주로 사용하고 while 문은 반복 횟수가 불명확할 때 주로 사용한다.

while 문은 조건문의 평가 결과가 거짓이 되면 코드 블록을 실행하지 않고 종료한다. 만약 조건식의 평가 결과가 불리언 값이 아니면 불리언 값으로 강제 변환하여 논리적 참, 거짓을 구별한다.

1
2
3
4
5
6
var count = 0;

while (count < 3) {
console.log(count); // 0 1 2
count++;
}

조건식의 평가 결과가 언제나 참이면 무한루프가 된다.
무한루프에서 탈출하기 위해서는 코드 블록 내에 if 문으로 탈출 조건을 만들고 break 문으로 코드 블록을 탈출한다.

1
2
3
4
5
6
7
8
9
var count = 0;

// 무한루프
while (true) {
console.log(count); // 0 1 2
count++;

if (count === 3) break;
}

do…while 문

do…while 문은 코드 블록을 먼저 실행하고 조건식을 평가한다. 따라서 코드 블록은 무조건 한 번 이상 실행된다.

1
2
3
4
5
6
var count = 0;

do {
console.log(count); // 0 1 2
count++;
} while (count < 3);

break 문

switch 문과 while 문에서 살펴보았듯이 break 문은 코드 블록을 탈출한다.
더 정확히 말하자면 레이블 문, 반복문 또는 switch 문의 코드 블록을 탈출한다. 레이블 문, 반복문, switch 문의 코드 블록 외에 break 문을 사용하면 SyntaxError가 발생한다.

1
2
3
if (true) {
break; // Uncaught SyntaxError: Illegal break statement
}

참고로 레이블 문은 식별자가 붙은 문을 말한다.
레이블 문은 프로그램의 실행 순서를 제어하는데 사용한다. 사실 switch 문의 case 문과 default 문 역시 레이블 문이다.
레이블 문을 탈출하려면 break 문에 레이블 식별자를 지정한다.

1
2
3
4
5
6
7
8
9
foo: {
console.log(1);
break foo;
console.log(2);
}

console.log('Done!');
// 1
// Done!

레이블 문을 사용하면 프로그램의 흐름이 복잡해져서 가독성이 나빠지고 오류를 발생시킬 가능성이 높아지기 때문에 일반적으로 권장하지 않는다.

continue 문

continue 문은 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킨다.
break 문처럼 반복문을 탈출하지는 않는다.

다음은 문자열에서 특정 문자의 개수를 세는 예다.

1
2
3
4
5
6
7
8
9
10
var string = 'Hello World.';
var search = 'l';
var count = 0;

for (var i = 0; i < string.length; i++) {
if (string[i] !== search) continue; // 'l'이 아니면 현 지점에서 실행을 중단하고 반복문의 증감식으로 이동한다.
count++; // continue 문이 실행되면 이 문은 실행되지 않는다.
}

console.log(count); // 3

연산자

연산자는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수 연산 등을 수행해 하나의 값을 만든다.

이 때 연산의 대상을 피연산자라 한다. 피연산자는 값으로 평가될 수 있는 표현식이어야 한다.
그리고 피연산자와 연산자의 조합으로 이뤄진 연산자 표현식도 값으로 평가될 수 있는 표현식이다.

자바스크립트가 제공하는 다양한 연산자에 대해 살펴보자.

산술 연산자

산술 연산자는 피연산자를 대상으로 수학적 계산을 수행해 새로운 숫자 값을 만든다.
산술 연산이 불가능한 경우, NaN을 반환한다.

산술 연산자는 피연산자의 개수에 따라 이항 산술 연산자와 단항 산술 연산자로 구분할 수 있다.

이항 산술 연산자

이항 산술 연산자는 2개의 피연산자를 산술 연산하여 숫자 값을 만든다.

모든 이항 산술 연산자는 피연산자의 값을 변경하는 부수효과가 없다.

1
2
3
4
5
5 + 2; // 7
5 - 2; // 3
5 * 2; // 10
5 / 2; // 2.5
5 % 2; // 1

단항 산술 연산자

단항 산술 연산자는 1개의 피연산자를 산술 연산하여 숫자 값을 만든다.

이 때 증가/감소(++/–) 연산자는 피연산자의 값을 변경한느 부수 효과가 있어 주의해야 한다.
즉 증가/감소 연산을 하면 피연산자의 값을 변경하는 암묵적 할당이 이뤄진다.

1
2
3
4
5
6
7
var x = 1;

x++; // x = x + 1;
console.log(x); // 2

x--; // x = x - 1;
console.log(x); // 1

또한 증가/감소 연산자는 위치에 따라 선 할당후 연산이 이루어지는지, 연산이 수행되고 할당이 이루어지는지 다르므로 주의해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var x = 5,
result;

result = x++; // 선할당 후증가
console.log(result, x); // 5 6

result = ++x; // 선증가 후할당
console.log(result, x); // 7 7

result = x--; // 선할당 후감소
console.log(result, x); // 7 6

result = --x; // 선감소 후할당
console.log(result, x); // 5 5

+ 단항 연산자는 피연산자에 어떠한 효과도 없다.
다만 숫자 타입이 아닌 피연산자에 + 단항 연산자를 사용하면 피연산자를 숫자 타입으로 변환하여 반환한다.
이때 피연산자를 변경하는 것은 아니고 숫자 타입으로 변환한 값을 생성해서 반환한다. 즉 부수 효과가 없다.

1
2
3
4
var x = '1';

console.log(+x); // 1
console.log(x); // "1"

- 단항 연산자는 피연산자에 부호를 반전한 값을 반환한다.
+ 단항 연산자와 마찬가지로 숫자타입이 아닌 피연산자에 사용하면 피연산자를 숫자 타입으로 변환하여 반환한다.
이때 피연산자를 변경하는 것은 아니고 숫자 타입으로 변환한 값을 생성해서 반환한다. 즉 부수 효과가 없다.

1
2
-(-10); // 10 (부호 반전)
-true; // -1 (타입 변환)

문자열 연결 연산자

+ 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작한다.
그 외의 경우는 산술 연산자로 동작한다.

1
2
3
4
5
6
7
8
9
10
// 문자열 연결 연산자
'1' + 2; // "12"
2 + '1'; // "12"

// 산술 연산자
1 + 2; // 3

// 타입 변환이 일어나는 경우
1 + true; // 2
1 + undefined; // NaN

이 때 주목할 점은 개발자의 의도와는 상관 없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되기도 한다는 것이다.
1 + true를 연산하면 자바스크립트 엔진은 암묵적으로 불리언 타입 값인 true를 숫자 탕비인 1로 강제로 변환한 후 연산을 수행한다.

이를 암묵적 타입 변환이라고 한다. 앞서 살펴본 +/- 단항 연산자도 암묵적 타입 변환이 발생한 것이다

할당 연산자

할당 연산자는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당한다.
할당 연산자는 좌항의 변수에 값을 할당하므로 변수 값이 변하는 부수 효과가 존재한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var x;

x = 10;
console.log(x); // 10

x += 5; // x = x + 5;
console.log(x); // 15

x -= 5; // x = x - 5;
console.log(x); // 10

x *= 5; // x = x * 5;
console.log(x); // 50

x /= 5; // x = x / 5;
console.log(x); // 10

x %= 5; // x = x % 5;
console.log(x); // 0

표현식은 값으로 평가 될 수 있는 문이고, 문에는 표현식인 문과 표현식이 아닌 문이 존재한다.
그렇다면 할당문은 표현식인 문일까, 표현식이 아닌 문일까?

할당문은 변수에 값을 할당하는 부수 효과만 있을 뿐 값으로 평가되지 않을 것처럼 보인다.
하지만 할당문은 값으로 평가되는 표현식인 문으로서 할당된 값으로 평가된다. 즉 변수에 할당할 수 있다.
이러한 특징을 활용해 여러 변수에 동일한 값을 연쇄 할당할 수 있다.

1
2
3
4
5
var a, b, c;

a = b = c = 0;

console.log(a, b, c); // 0 0 0

비교 연산자

비교 연산자는 좌항과 우항의 피연산자를 비교한 다음 그 결과를 불리언 값으로 반환한다.
비교 연산자는 if 문이나 for 문과 같은 제어문의 조건식에서 주로 사용한다.

동등/일치 비교 연산자

동등 비교 연산자와 일치 비교 연산자는 좌항과 우항의 피연산자가 같은 값으로 평가되는지 비교해 불리언 값을 반환한다.
하지만 비교하는 엄격성의 정도가 다르다.

동등 비교(==) 연산자는 좌항과 우항의 피연산자를 비교할 때 먼저 암묵적 타입 변환을 통해 타입을 일치시킨 후 같은 값인지 비교한다.
다시 말해 동등 비교 연산자는 좌항과 우항의 피연산자가 타입은 다르더라도 암묵적 타입 변환 후에 같은 값일 수 있다면 true를 반환한다.

동등 비교 연산자는 편리한 경우도 있지만 결과를 예측하기 어렵고 실수하기 쉬우므로 사용하지 않는 편이 좋다.

반면 일치 비교(===) 연산자는 좌항과 우항의 피연산자가 타입도 같고 값도 같은 경우에 한하여 true를 반환한다. 따라서 일치 비교 연산자는 예측하기 쉽다.

1
2
3
4
5
5 == 5; // true
5 == '5'; // true (암묵적 타입 변환)

5 === 5; // true
5 === '5'; // false (타입 변환 X)

일치 비교 연산자에서 주의할 것은 NaN이다.

1
NaN === NaN; // false

NaN은 자신과 일치하지 않는 유일한 값이다. 숫자가 NaN인지 조사하기 위해서는 빌트인 함수 Number.isNaN을 사용한다.

1
2
Number.isNaN(NaN); // true
Number.isNaN(10); // false

ES6에서 도입된 Object.is 메서드를 이용하면 예측 가능한 정확한 비교 결과를 얻을 수 있다.

1
2
3
4
5
-0 === +0; // true
Object.is(-0, +0); // false

NaN === NaN; // false
Object.is(NaN, NaN); // true

부동등 비교 연산자(!=)와 불일치 비교 연산자(!==)는 각각 동등 비교(==) 연산자와 일치 비교(===) 연산자의 반대 개념이다.

대소 관계 비교 연산자

대소 관계 비교 연산자는 피연산자의 크기를 비교하여 불리언 값을 반환한다.

1
2
3
4
5 > 0; // true
5 > 5; // false
5 >= 5; // true
5 <= 5; // true

삼항 조건 연산자

삼항 조건 연산자는 조건식의 평가 결과에 따라 반환할 값을 결정한다.
자바스크립트의 유일한 삼항 연산자이며, 부수 효과는 없다.

삼항 조건 연산자의 표현식은 다음과 같다.

조건식 ? 조건식이 true일 때 반환할 값 : 조건식이 false일 때 반환할 값

삼항 조건 연산자는 첫 번째 피연산자가 true로 평가되면 두 번째 피연산자를 반환하고, 첫 번째 피연산자가 false로 평가되면 세 번째 피연산자를 반환한다.
즉 삼항 조건 연산자는 두 번째 피연산자 또는 세 번째 피연산자로 평가되는 표현식이다.

삼항 조건 연산자는 if…else 문을 사용해도 유사하게 처리 가능하다. 하지만 삼항 조건 연산자 표현식은 값처럼 사용할 수 있는 반면 if…else 문은 값처럼 사용할 수 없다는 차이가 있다.

1
2
3
4
5
6
var x = 10;

var result1 = x % 2 ? '홀수' : '짝수';
console.log(result1); // 짝수

var result2 = if (x % 2) { result2 = '홀수'; } else { result2 = '짝수'; }; // SyntaxError: Unexpected token if

조건에 따라 어떤 값을 결정해야 한다면 if…else 문보다 삼항 조건 연산자 표현식을 사용하는 편이 유리하다.
하지만 조건에 따라 수행해야 할 문이 하나가 아니리 여러 개라면 if…else 문의 가독성이 더 좋다.

논리 연산자

논리 연산자는 우항과 좌항의 피연산자(부정 논리 연산자의 경우 우항의 피연산자)를 논리 연산 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 논리합(||) 연산자
true || true; // true
true || false; // true
false || true; // true
false || false; // false

// 논리곱(&&) 연산자
true && true; // true
true && false; // false
false && true; // false
false && false; // false

// 논리 부정(!) 연산자
!true; // false
!false; // true

논리 부정(!) 연산자는 언제나 불리언 값을 반환한다. 단 피연산자가 반드시 불리언 값일 필요는 없다.
피연산자가 불리언 값이 아니라면 불리언 타입으로 암묵적 타입 변환된다.

쉼표 연산자

쉼표(,) 연산자는 왼쪽 피연산자부터 차레대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.

1
2
3
var x, y, z;

(x = 1), (y = 2), (z = 3); // 3

그룹 연산자

소괄호(())로 피연산자를 감싸는 그룹 연산자는 자신의 피연산자인 표현식을 가장 먼저 평가한다. 따라서 그룹 연산자를 사용하면 연산자의 우선순위를 조절할 수 있다. 그룹 연산자는 연산자 우선순위가 가장 높다.

typeof 연산자

typeof 연산자는 피연산자의 데이터 타입을 문자열로 반환한다.
typeof 연산자는 “number”, “bigint”, “string”, “boolean”, “undefined”, “symbol”, “object”, “function” 중 하나를 반환한다.
typeof 연산자가 반환하는 문자열이 데이터 타입과 정확히 일치하지는 않는다.

typeof 연산자로 null 값을 연산해 보면 “null”이 아닌 “object”를 반환하는데 이는 하위 호환성을 위해 수정되지 않고 있는 버그이다.
따라서 값이 null 타입인지 확인할 때는 typeof 연산자가 아닌 일치(===) 연산자를 사용해야 한다.

1
2
3
4
var foo = null;

typeof foo === null; // false
foo === null; // true

또한 선언하지 않은 식별자를 typeof 연산자로 연산해 보면 ReferenceError가 발생하지 않고 undefined를 반환하는것도 주의하자.

1
2
// undeclared 식별자를 선언하지 않았다.
typeof undeclared; // undefined

지수 연산자

ES7에서 도입된 지수 연산자는 좌항의 피연산자를 밑으로, 우항의 피연산자를 지수로 거듭 제곱한 숫자 값을 반환한다.

1
2
3
2 ** 2; // 4
2 * 0; // 1
2 ** -2; // 0.25

지수 연산자가 도입되기 이전에는 Math.pow 메서드를 사용했다.

1
2
3
Math.pow(2, 2); // 4
Math.pow(2, 0); // 1
Math.pow(2, -2); // 0.25

음수를 거듭제곱의 밑으로 사용해 계산하려면 괄호로 묶어주어야 한다.

1
2
3
4
-5 ** 2;
// SyntaxError: Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence

(-5) ** 2; // 25

연산자 우선순위

연산자 우선순위란 여러 개의 연산자로 이뤄진 문이 실행될 때 연산자가 실행되는 순서를 말한다. 우선순위가 높을수록 먼저 실행된다.

연산자는 종류가 많아 연산자 우선순위를 모두 기억하기 어렵고 실수하기도 쉽다.
따라서 기억해 사용하기 보다는 연산자 우선순위가 높은 그룹 연산자를 사용하여 우선순위를 명시적으로 조절하는 것이 좋다.

1
10 * (2 + 3); // 50

데이터 타입은 값의 종류를 말한다. 자바스크립트의 모든 값은 데이터 타입을 갖는다. 자바스크립트는 8개의 데이터 타입을 가지며 7개의 원시 타입(primitive type)과 1개의 객체 타입(object/reference type)으로 분류 가능하다.

데이터 타입의 종류에 따른 특징에 대해 살펴보고 데이터 타입이 왜 필요한지에 대해 알아보자.


먼저 원시 타입에 대해 알아보자.

원시 타입

원시 타입에는 숫자(Number) 타입, Bigint 타입, 문자열(String) 타입, 불리언(Boolean) 타입, undefined 타입, null 타입, 심벌(Symbol) 타입이 존재한다. 순서대로 각 타입에 대해 살펴보겠다.

숫자 타입

타 프로그래밍 언어에서는 정수(소수점 이하가 없는 숫자)와 실수(소수점 이하가 있는 숫자)를 구분해 int, long, float, double 등과 같은 다양한 숫자 타입을 제공한다. 하지만 자바스크립트는 독특하게 하나의 숫자 타입만 존재한다.

자바스크립트의 숫자 타입의 값은 배정밀도 64비트 부동소수점 형식을 따르기 때문에 모든 수를 실수로 처리한다.

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

  • Infinity : 양의 무한대
  • -Infinity : 음의 무한대
  • NaN : 산술 연산 불가(Not-a-Number)

자바스크립트는 대소문자를 구별하므로 NaN을 NAN,Nan,nan과 같이 표현하면 에러가 발생한다. 자바스크립트는 NAN,Nan,nan을 식별자로 해석한다.

1
var x = nan; // ReferenceError: nan is not defined

부동소수점 형식이란?

부동소수점 형식은 실수를 컴퓨터 상에서 근사하여 표현할 때 소수점의 위치를 고정하지 않고 그 위치를 나타내는 수를 따로 적는 것으로, 유효숫자를 나타내는 가수와 소수점의 위치를 풀이하는 지수로 나누어 표현한다.

몇 비트를 기반으로 하는지에 따라 정밀도가 달라지며 32비트를 단정밀도, 64비트를 배정밀도라 한다.

32비트의 경우는 부호 1비트 + 지수 8비트 + 가수 23비트로 나누어지며 64비트는 부호 1비트 + 지수 11비트 + 가수 52비트로 나누어진다.

지수부 계산시 bias를 더해줌으로 음수값을 가지는 지수와 양수값을 가지는 지수를 폭 넓게 표현 가능하다. (bias: 32비트-127, 64비트-1023)

Bigint 타입

자바스크립트의 숫자 타입은 64비트 부동소수점 형식의 2진수로 저장된다고 했다. 64비트 부동소수점 형식으로 안정적으로 나타낼 수 있는 최대치는 2^53^ - 1이다.

대부분의 소규모 프로그래밍이나 웹 개발에서 안정적으로 나타낼 수 있는 수의 최대치가 존재하는것이 큰 문제는 아니었다.
하지만 자바스크립트가 백엔드 언어로서도 점점 유용해지고 데이터베이스에서도 사용하게 되면서 큰 정수 ID나 고정밀 타임 스탬프를 표현해야 하는 일이 생기게 되었다.

ES11에서는 기존의 숫자 타입보다 큰 수를 표현할 수 있도록 새로운 원시 값인 Bigint 타입을 추가하게 되었다.

Bigint 값은 정수 리터럴 뒤에 n을 붙이거나 Bigint 함수를 호출해 생성할 수 있다.

1
2
var a = 10n;
var b = BigInt(10);

문자열 타입

문자열 타입은 텍스트 데이터를 나타내는 데 사용한다. 문자열은 0개 이상의 16비트 유니코드 문자(UTF-16)의 집합이다.

문자열은 작은따옴표(‘’), 큰 따옴표(“”), 백틱(``)으로 텍스트를 감싼다.

다른 타입의 값과 달리 문자열을 따옴표로 감싸는 이유는 키워드나 식별자와 같은 토큰과 구분하기 위함이다. 만약 문자열을 따옴표로 감싸지 않으면 자바스크립트 엔진은 이를 키워드나 식별자 같은 토큰으로 인식한다.

1
var str = hello; // ReferenceError: hello is not defined

템플릿 리터럴

ES6부터 템플릿 리터럴이라고 하는 새로운 문자열 표기법이 도입되었다.

템플릿(template)의 뜻은 주형이며 리터럴(literal)의 뜻은 정확한이다. 이를 해석해보면 정확한 주형이란 뜻이 되는데 템플릿 리터럴은 해석 그대로 리터럴 내부의 문자열을 정확히 표현한다고 생각하면 편하다.

템플릿 리터럴은 백틱(``)으로 감싸 표현하며 멀티라인 문자열, 표현식 삽입, 태그드 템플릿과 같은 편리한 문자열 처리 기능을 제공한다.

일반 문자열과는 어떤 차이가 있을까?

템플릿 리터럴은 멀티라인 문자열, 표현식 삽입과 같은 편리한 기능을 제공한다고 했다. 그럼 위 기능들이 제공되지 않는다는 소리인데 일반 문자열은 어떻게 개행과 문자열의 연결을 수행했을까?

일반 문자열은 공백과 같은 특수 문자를 표현하기 위해 백스페이스()로 시작하는 이스케이프 시퀀스를 이용한다.

  • \0(null), \b(백스페이스), \f(폼 피드, 프린터로 출력시 다음 페이지 시작지점으로 이동), \n(개행 LF), \r(개행 CR), \t(수평 탭), \v(수직 탭), \'(작은 따옴표), \"(큰 따옴표), \\(백슬래시)

일반 문자열은 문자열 연결 연산자 +를 이용하지만 템플릿 리터럴은 표현식 삽입(${})을 이용해 문자열을 연결한다.

1
2
3
4
5
6
var fisrst = 'rong';
var last = 'rong';

console.log('My name is ' + first + last + '.'); // ES5의 문자열 연결, My name is rongrong.

console.log(`My name is ${first}${last}.`); // ES6 템플릿 리터럴의 표현식 삽입, My name is rongrong.

불리언 타입

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

불리언 타입의 값은 참과 거짓으로 구분되는 조건에 의해 프로그램의 흐름을 제어하는 조건문에서 자주 사용된다.

undefined 타입

undefined 타입의 값은 undefined가 유일하다.

앞서서 var 키워드로 선언한 변수는 암묵적으로 undefined로 초기화 되는것을 살펴보았다.
이처럼 undefined는 개발자가 의도적으로 할당하기 위한 값이 아니라 자바스크립트 엔진이 변수를 초기화 할 때 사용하는 값이다.
변수를 참조했을 때 undefined가 반환된다면 참조한 변수가 선언 이후 초기화되지 않은 변수임을 알 수 있다.

undefined를 개발자가 의도적으로 변수에 할당하는 것은 본래 취지와 어긋날뿐더러 혼란을 줄 수 있으므로 좋지 않다.

그렇다면 변수에 값이 없다는 것을 명시하고 싶을 때는 어떻게 하면 좋을까? null을 할당해주면 된다.

null 타입

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

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

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

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<script>
var element = document.querySelector('.myClass');

console.log(element); // null
</script>
</body>
</html>

심벌 타입

심벌은 ES6에서 추가된 타입으로, 변경 불가능한 원시 타입의 값이다. 심벌 값은 다른 값과 중복되지 않는 유일무이한 값이다.
따라서 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해 사용한다.

심벌값은 리터럴이 아닌 Symbol 함수를 호출해 생성한다.


객체 타입

자바스크립트는 크게 원시 타입과 객체 타입으로 분류한다. 이는 객체 타입이 다른 원시 타입들과는 다른 특성을 가짐을 의미한다.

객체 타입에 대해서는 다음에 자세하게 알아보도록 하겠다.


지금 까지 자바스크립트의 8가지 데이터 타입에 대해 알아보았다. 그렇다면 데이터 타입은 왜 필요한 것일까?

데이터 타입의 필요성

값은 메모리에 저장하고 참조할 수 있어야 한다. 값을 메모리에 저장하기 위해서는 값이 들어가기 위한 메모리 공간의 크기를 결정해야 한다.
즉 몇 바이트의 메모리 공간을 사용해야 낭비와 손실 없이 값을 저장할 수 있는지 알아야 한다.

다음과 같은 코드를 실행해보자.

1
var score = 100;

자바스크립트 엔진은 정수 리터럴 100을 평가해 숫자 타입의 값 100을 만들어 score라는 변수에 할당한다.
자바스크립트의 숫자 타입은 64비트 부동소수점 방식을 따른다고 했으니 숫자 타입의 값을 저장하기 위해서는 8바이트의 공간이 필요할 것이다.

이처럼 자바스크립트 엔진은 데이터 타입, 즉 값의 종류에 따라 정해진 크기의 메모리 공간을 확보한다.

이번에는 값을 참조하는 경우를 생각해보자. 식별자 score를 통해 숫자 타입의 값 100이 저장되어 있는 메모리 공간의 주소를 찾아갈 수 있다.
이 때 값을 참조하려면 한 번에 읽어 들여야 할 메모리 공간의 크기를 알아야 한다. score 변수의 경우, 저장되어 있는 값이 숫자 타입이므로 8바이트 단위로 읽어 들이지 않는다면 값이 훼손될 것이다.

역시 자바스크립트 엔진은 데이터 타입을 통해 한번에 읽어 들여야 할 메모리 공간의 크기를 결정한다.

아직 문제가 남아 있다. 메모리에서 읽어 들인 2진수를 어떻게 해석해야 하느냐다.
모든 값은 데이터 타입을 가지며 메모리에 2진수로 저장된다. 메모리에 저장된 값은 데이터 타입에 따라 다르게 해석될 수 있다. 예를 들어, 메모리에 저장된 값 0100 0001을 숫자로 해석하면 65지만 문자열로 해석하면 ‘A’이다.

자바스크립트 엔진은 데이터 타입에 따라 메모리 공간에서 읽어들인 2진수를 해석한다.

정리하자면 다음과 같은 이유로 데이터 타입이 필요하다고 할 수 있다.

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

자바스크립트의 모든 값은 데이터 타입을 가진다. 그렇다면 변수 역시 데이터 타입을 가질까?

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

정적 타입 언어는 변수의 타입을 변경할 수 없으며, 변수에 선언한 타입에 맞는 값만 할당할 수 있다.

자바스크립트는 정적 타입 언어와 다르게 변수를 선언할 때 타입을 선언하지 않고 var, let, const 키워드만 사용해 변수를 선언한다.
자바스크립트의 변수는 어떠한 데이터 타입의 값이라도 자유롭게 할당할 수 있다. 따라서 자바스크립트의 데이터 타입은 정적 타입 언어의 데이터 타입과는 개념이 다르다.

자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정(타입 추론)된다. 그리고 재할당에 의해 변수의 데이터 타입이 언제든지 동적으로 변할 수 있다. 이러한 특징을 동적 타이핑이라 하며, 정적 타입 언어와 구분하기 위해 동적 타입 언어라 한다.

동적 타입 언어는 변수에 어떤 데이터 타입의 값이라도 자유롭게 할당 가능하므로 편리하다. 하지만 이로 인한 구조적인 단점 역시 존재한다.
변수 값은 언제든지 변경될 수 있기 때문에 복잡한 프로그램에서는 변화하는 변수 값을 추적하기 어려울 수 있다.

그 뿐만 아니라 값의 변경에 의해서도 타입도 언제든 변경될 수 있다. 따라서 동적 타입 언어의 변수는 값을 확인하기 전 타입을 확신할 수 없다.
또 자바스크립트는 개발자의 의도와는 관계없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환하는 경우도 존재한다.

이처럼 동적 타입 언어는 유연성은 높지만 신뢰성은 떨어진다.

이러한 단점을 해결하기 위해 자바스크립트를 정적 타입 언어처럼 사용할 수 있는 타입스크립트가 등장했다. 웹의 규모가 커지면서 더 복잡한 프로그램을 만들어야 하는 경우가 많아지고 있고 따라서 타입스크립트를 다룬 경험을 요구하는 기업 또한 많아지고 있다. 자바스크립트의 동적인 부분이 단점으로 다가오는 경우가 많은것 같다. 이런 부분에 대해서도 고민이 필요할 것 같다.