JS 데이터 타입

김태환·2023년 2월 26일
post-thumbnail

6장 데이터 타입

데이터 타입? 변수에 할당된 값의 종류이다. Javascript(ES6 기준)는 7개의 데이터 타입을 제공하며, 이를 원시 타입primitive type과 객체 타입object/reference type으로 분류할 수 있다.

원시 타입

  • 숫자number 타입: 숫자 및 정수와 실수 구분 없이 하나의 숫자 타입.
  • 문자열string 타입: 문자열.
  • 불리안boolean 타입: 논리적 참(true), 거짓(false).
  • undefined 타입: 선언된 변수에 암묵적으로 할당되는 값.
  • null 타입: 값이 없다는 것을 명시할 때 사용하는 값(undefined와 다름).
  • 심벌symbol 타입: 변경이 불가능한 원시 값으로 객체의 (고유한) 프로퍼티 값으로 사용.

객체 타입

  • 객체 타입: 객체, 함수, 배열 등.

6.1 숫자 타입

Java, C++는 int, long, float, double 등 정수와 실수를 구분해서 숫자 타입을 제공한다.

Javascript에서는 모두 하나의 숫자 타입이며, 이 숫자 타입의 값은 배정밀도 64비트 부동소수점 형식을 따른다.

64비트 부동소수점 형식? 비트부호(1) + 지수부(11) + 가수부(52) 로 구성되어, 각각 양수/음수 부호, 소수점 위치, 숫자를 담고 있다.

이 때, 모든 수를 실수로 처리한다는 특징이 있다. 따라서, 정수만을 표현하는 데이터 타입integer type은 존재하지 않는다.

let integer = 10; //정수
let double = 10.12; //실수
let binary = 0b01000001 // 2진수
let octal = 0o101;  //8진수
let hex = 0x41; //16진수

위에 선언한 변수들(정수, 실수, 2진수, 8진수, 16진수 리터럴을 담고 있는)은 모두 메모리에 배정밀도 62비트 부동소수점 형식의 2진수로 저장된다.

하지만, 2진수와 같은 표현을 위한 데이터 타입은 제공하지 않으므로 이와 같은 값을 참조하면 아래 코드 예시처럼 모두 10진수로 해석된다.

console.log(hex); // 65

Javascript의 숫자 타입은 모든 수를 실수로 처리하는 점에서 특이점이 있다. 아래 코드처럼 정수와 실수의 비교 연산이 가능하고, 정수끼리 나눠도 실수가 나오는 등이 있다.

console.log(1 === 1.0); // true
console.log(5 / 2); // 2.5
console.log(10 / 0); // Infinity
console.log(10 / -0); // -Infinity
console.log(1 * 'hello'); // NaN

이 외에도 양/음의 무한대(-)Infinity, 산술 연산 불가 수NaN와 같이 특별한 값에 대한 표현도 존재한다.


6.2 문자열 타입

문자열string 타입은 텍스트 데이터를 나타내는 데 사용한다.

Javascript에서 문자열을 표현하기 위해서는 작은따옴표(''), 큰따옴표(""), 백틱(``)으로 텍스트를 감싼다. - 주로 작은따옴표 활용.

let string1 = 'string';
let string2 = "string";
let string3 = `string`;
let singleQuote = '작은따옴표로 감싼 "큰따옴표"는 문자열로 인식';
let doubleQuote = "반대로 큰따옴표로 감싼 '작은따옴표'는 문자열로 인식";
// let string = hello;

위 코드 예시처럼 문자열을 활용할 수 있지만, 따옴표로 감싸지 않은 마지막 줄 같은 경우에는 식별자로 인식하여 ReferenceError를 발생시킨다.

문자열 변경 불가능한 값immutable value? Javscript에서 문자열은 원시 타입으로, 변경이 불가능하다. 이는 11장 "문자열과 불변성"에서 보자.


6.3 템플릿 리터럴

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

템플릿 리터럴은 멀티라인 문자열multi-line string, 표현식 삽입expression interpolation, 태그드 템플릿tagged template 등 편리한 문자열 처리 기능을 제공한다.

이들은 모두 런타임(실행시간)에 일반 문자열로 반환되어 처리된다.

템플릿 리터럴은 6.2장에서 일반 문자열에 사용하는 따옴표 대신 백틱(``)을 사용하여 표현한다.

6.3.1 멀티라인 문자열

let str = 'Hello
world!';

위 코드 예시처럼 일반 문자열 내에서는 줄바꿈이 허용되지 않아, 아래 코드 예시와 같이 백슬래시()로 시작하는 이스케이프 시퀀스escape sequence를 사용해야 한다.

let str = 'Hello \n world!';

하지만 템플릿 리터럴 내에서는 아래 코드처럼 이스케이프 시퀀스 없이 모두 있는 그대로 적용된다.

let template = `Hello
  world!`;
// 출력
Hello
  wolrd!

6.3.2 표현식 삽입

문자열은 문자열 연산자 +를 통해 연결이 가능하다.

연산자의 피연산자 중 하나라도 문자열인 경우 아래 코드 예시처럼 문자열 연결로 처리된다.

// Today is 5th of December
let str = 'Today is ' + 5 + 'th of December';

하지만 + 연산자는 문자열 연결 연산자인 동시에 덧셈 연산자이므로 가독성이 좋지 않다.

템플릿 리터럴 내에서는 표현식 삽입expression interpolation을 통해 간단히 문자열을 삽입할 수 있으며, 가독성 좋고 간편하게 문자열 조합이 가능하다.

const firstName = "Tae Hwan";
const lastName = "Kim";

// My name is Tae Hwan Kim.
console.log(`My name is ${firstName} ${lastName}.`);

위 코드 예시처럼 표현식 삽입에는 ${}으로 표현식을 감싼다.

표현식의 결과가 문자열이 아니라면 문자열로 타입이 강제로 변환되어 삽입된다.

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

6.4 불리언 타입

불리언boolean 타입의 값은 true(논리적 참), false(논리적 거짓) 2개 뿐이다.

불리언 타입의 값은 주로 조건문에 사용하여 조건에 의한 프로그램의 흐름을 제어하는 데에 활용한다.


6.5 undefined 타입

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

let, let 키워드로 선언한 변수는 암묵적(묵시적, 명령하지 않아도)으로 undefined로 초기화된다.

반면, const 키워드로 선언하는 경우에는 선언문과 할당문을 같이 작성하여 무조건 변수의 값을 할당하여 초기화해야한다. Ex) const birth = 1998;

변수 선언에 의해 확보된 메모리 공간이 처음 할당될 때 빈 상태 또는 쓰레기 값으로 놔두지 않고, undefined로 초기화하는 것이다.

따라서, undefined를 마주하게 된 경우, 이가 참조한 변수가 선언 이후 값이 할당된 적이 없음을 알 수 있다.

변수에 값이 없음을 명시하고 싶으면? undefined 대신 null을 할당한다.


6.6 null 타입

null 타입의 값은 null이 유일하다.

주의해야 할 것은 Javscript는 대소문자를 구별하므로 null은 Null, NULL 등과 다르다.

프로그래밍 언어에서 null은 변수에 값이 없다, 이전 참조 값을 더 이상 참조하지 않겠음을 의도적으로 명시(의도적 부재intentional absence)할 때 사용한다.

이 때, 참조하지 않게 되는 메모리 공간은 가비지 콜렉션이 수행될 것이다.

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

let element = document.querySelector('.myClass');
// HTML 문서에 myClass 클래스를 갖는 요소가 없다면 null 반환.
console.log(element); // null

6.7 심벌 타입

심벌symbol은 변경 불가능한 원시 타입의 값, 다른 값과 중복되지 않는 유일무이한 값이다.

따라서, 심벌 타입은 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들 때 사용한다.

심벌은 다른 원시 값이 리터럴을 통해 생성되는 것과 달리 Symbol()를 호출해 생성한다.

이 때, 생성되는 심벌 값은 외부에 노출되지 않고, 유일무이한 값이다.

let a = Symbol('key');
let b = Symbol('key');
const obj = {};
obj[a] = 'valueA';
obj[b] = 'valueB';
console.log(obj[a]);  // valueA
console.log(obj[b]);  // valueB

위 코드에서 a, b가 같은 값으로 보이지만 심벌 타입으로 유일무이한 값이므로 a === b의 연산 결과값은 false이며, obj[a], obj[b]도 서로 다른 것을 저장한다.

33장에 이어서 보자.


6.8 객체 타입

Javscript의 데이터 타입은 원시 타입과 객체 타입으로 분류한다.

원시 타입과 객체 타입은 근본적으로 다를 것이기 때문이라 예측하나, 이는 11장 "원시 값과 객체의 비교"에서 보자.


6.9 데이터 타입의 필요성

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

값은 메모리에 저장하고 참조할 수 있어야 한다.

이 때, 먼저 확보해야 할 메모리 공간의 크기를 결정해야 낭비와 손실 없이 값을 저장할 수 있다.

let score = 100;

위 코드에서 숫자 값 100을 저장하기 위해 메모리 공간을 확보한 후, 그 메모리에 숫자 값 100을 2진수로 저장한다.

이 때, 얼마 만큼의 메모리 공간을 확보할 지 알아야 하는데, Javascript 엔진은 데이터 타입에 따라 정해진 크기의 메모리 공간을 확보한다.

Javascript 엔진은 레터럴 100을 숫자 타입의 값으로 해석하고 이를 저장하기 위해 8바이트의 메모리 공간을 확보하고 100을 2진수로 저장한다.

반대로, 값을 참조할 때는 식별자 score를 통해 메모리 공간의 주소(정확히는 선두 메모리 셀의 주소)를 찾아갈 수 있다.

이 때, 값을 참조하려면 한 번에 읽어 들여야 할 메모리 공간의 크기(메모리 셀의 개수, 바이트 수)를 알아야 값의 훼손 없이 읽어들일 수 있다.

마찬가지로, Javascript 엔진은 score 변수의 데이터 타입을 통해 읽어 들일 메모리 공간의 크기를 알아낸다.

심벌 테이블? 컴파일러는 심벌 테이블이라는 자료구조를 통해 식별자를 키로 바인딩된 값의 메모리 주소, 데이터 타입, 스코프(유효 범위) 등을 관리한다.

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

메모리에서 읽어 들인 2진수는 어떻게 해석할까?

0100 0001을 숫자로 해석하면 65, 문자열로 해석하면 'A'이다.

따라서, 어떻게 해석할 지를 정할 때 해당 변수의 데이터 타입을 따라 정하게 된다.

데이터 타입이 필요한 이유 정리

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

6.10 동적 타이핑

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

C++, Java 같은 정적 타입static/strong type 언어는 변수를 선언할 때 데이터 타입을 함께 선언해야 한다. Ex) int age;

이를 명시적 타입 선언explicit type declaration이라 한다.

정적 타입 언어는 변수의 타입을 변경할 수 없어, 해당 데이터 타입에 맞는 값만 할당할 수 있다.

컴파일 시점에 타입 체크type check를 수행하여 불통 시에 에러를 발생시키고 프로그램 실행을 막는다.

Javscript는 변수를 선언할 때 데이터 타입을 선언하지 않는다.

미리 선언한 데이터 타입의 값만 할당할 수 있는 것이 아니라는 점에서 정적 타입 언어와 다르며, 아래 코드 예시처럼 어떠한 데이터 타입의 값이라도 얼마든지 재할당할 수 있다.
| foo 값 | typeof(데이터 타입) foo |
|--------|----------------------|
| | undefined |
| 3 | number |
| 'taehwan' | string |
| true | boolean |
| null | object |
| Symbol() | symbol |
| {} | object |
| [] | object |
| function(){} | function |

따라서, Javscript의 변수는 선언이 아닌 할당에 의해 타입이 결정(타입 추론type inference)된다.

또, 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다.

이는 동적 타입dynamic/weak type 언어라 한다.

변수는 타입을 갖지 않지만, 변수에 할당된 값은 타입을 갖고, 이 값에 의해 변수의 타입이 동적으로 결정되는 것이다. 변수는 값에 묶여 있는 값에 대한 별명이라 생각하자.

6.10.2 동적 타입 언어와 변수

동적 타입 언어는 데이터 타입으로부터 자유롭기 때문에 편리하긴 하나, 이로 인한 구조적인 단점 역시 존재한다.

변수의 값이 언제든지 변경될 수 있어 복잡한 프로그램에서는 변수 값 추적이 어려울 수 있다.

또, 값이 변하기 때문에 변수의 타입 역시 동적으로 변하여 변수의 값을 확인하기 전에는 해당 변수의 데이터 타입을 확신할 수 없다. (심지어 Javscript 엔진은 암묵적으로 타입이 자동 변환하기까지 한다.)

이런 단점으로 인해 동적 타입 언어는 유연성은 높지만 신뢰성이 떨어진다.

따라서, 이러한 문제점들을 방지하기 위해 주의사항을 따라야 한다.

  • 변수는 꼭 필요한 경우에 한해 제한적으로 사용한다.
  • 변수의 유효 범위(스코프)는 최대한 좁게 만들어 변수의 부작용을 억제해야 한다.
  • 전역 변수는 최대한 사용하지 않는다.
  • 변수(let, var)보다는 상수(const)를 사용해 값의 변경을 억제한다.
  • 변수, 함수, 클래스 등의 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍한다.

이처럼 코드는 오해(커뮤니케이션 어려움의 원인 -> 생산성 저하, 팀 사기 저하)하지 않도록 작성해야 하며, 사람이 이해할 수 있는 코드, 가독성이 좋은 코드가 좋은 코드임을 명심하자.

"컴퓨터가 이해하는 코드는 어떤 바보도 쓸 수 있다. 하지만 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 쓴다." -Martin Fowler




👀 되돌아보기

  • 숫자 타입은 하나, 모두 실수로 처리

    Javascript의 숫자 관련 데이터 타입은 하나이며, 모두 실수로 처리

  • 템플릿 리터럴

    • 백틱(``)을 사용하며, 문자열을 표현할 때, 이스케이프 시퀀스 없이도 모두 있는 그대로 적용
    • 템플릿 리터럴 내에 표현식 삽입을 위해서는 해당 표현식을 ${}로 감쌈
  • 심벌 타입

    다른 값과 중복되지 않는 유일무이한 값, 같은 value를 가진 값으로 생성하여도 그 둘은 동일한 값을 갖지 않음

  • 데이터 타입의 필요성

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

    • Javscript의 변수는 선언이 아닌 할당에 의해 타입이 결정(타입 추론type inference)
    • 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있음
  • 동적 타입 언어의 유연성의 이면

    • 변경이 가능한 변수의 값과 데이터 타입에 의해 유연성이 높지만, 추적이 어렵고 타입 확신을 못해 신뢰성이 떨어진다.
    • 주의사항:
      • 변수는 꼭 필요한 경우에 한해 제한적으로 사용
      • 변수의 유효 범위(스코프)는 최대한 좁게 만들어 변수의 부작용을 억제
      • 전역 변수는 사용을 지양
      • 변수(let, var)보다는 상수(const)를 사용해 값의 변경을 억제
      • 변수, 함수, 클래스 등의 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍

내용 출처: [위키북스] 모던 자바스크립트 Deep Dive

profile
이로운 개발자

0개의 댓글