자바스크립트 데이터 타입은 코드를 배울 때 가장 먼저 외우는 개념이다. 그런데 오래된 강의나 블로그를 보면 “원시 타입은 6개”라고 적혀 있다. 지금은 틀린 말이다. 자바스크립트 데이터 타입은 ES2020을 기점으로 원시 타입 7개에 객체 하나로 바뀌었다. 숫자 하나로 모든 수를 버티던 언어가 왜 새 타입을 들였는지, 그리고 타입 변환에서 왜 자꾸 엉뚱한 결과가 나오는지를 알면 디버깅 시간이 확 줄어든다.

자바스크립트 데이터 타입은 이제 7개의 원시 타입
원시 타입(primitive type)은 객체가 아니면서 메서드도 프로퍼티도 갖지 않는 값이다. 한번 만들어지면 바뀌지 않고(불변), 값 자체로 비교된다. 현재 기준 원시 타입은 일곱이다.
- 숫자(Number)
- 문자열(String)
- 불리언(Boolean)
- null
- undefined
- 심볼(Symbol)
- BigInt
여기에 객체(Object) 하나가 더해진다. 객체는 여러 값이나 복잡한 구조를 담는 컨테이너다. 배열, Date, RegExp, Map, Set 모두 객체 계열이다. 정리하면 원시 7 + 객체 1, 이게 오늘의 MDN 공식 문서가 말하는 자바스크립트의 타입 구조다. 6개로 외웠다면 BigInt 하나를 추가하면 된다. 언어가 이런 식으로 계속 바뀌어 온 이유가 궁금하면 자바스크립트의 변화와 그 진화의 이유 11가지를 같이 보면 흐름이 잡힌다.
숫자 하나로 버티던 시절, 그리고 BigInt
자바스크립트는 10진수, 2진수, 8진수, 16진수를 모두 인식한다. 정수든 실수든, 지수 표기법이든 다 받는다. 무한대와 음의 무한대, 숫자가 아님을 뜻하는 NaN까지 특수 값으로 갖고 있다.
let count = 10; // 10진수
const blue = 0x0000ff; // 16진수
const umask = 0o0022; // 8진수
const temperature = 21.5; // 소수점 있는 실수
const c = 3.0e6; // 지수
const inf = Infinity; // 무한대
const nan = NaN; // 숫자 아님
문제는 이 모든 숫자가 결국 하나의 형식, 64비트 부동소수점(double)으로 저장된다는 점이다. 그래서 안전하게 다룰 수 있는 정수는 Number.MAX_SAFE_INTEGER인 9,007,199,254,740,991까지다. 이 선을 넘으면 계산이 슬그머니 어긋난다.
console.log(9007199254740991 + 1); // 9007199254740992
console.log(9007199254740991 + 2); // 9007199254740992 (또 같은 값!)
예전 글들이 “자바스크립트로는 정밀한 정수 연산이 어렵다”고 적은 게 바로 이 한계 때문이다. 그런데 이제는 답이 있다. ES2020에 들어온 BigInt다. 숫자 끝에 n을 붙이면 크기 제한 없이 정수를 다룬다.
const big = 9007199254740991n;
console.log(big + 1n); // 9007199254740992n
console.log(big + 2n); // 9007199254740993n (정확하다)
console.log(typeof big); // "bigint"
암호화 키나 거대한 ID, 정밀한 금액 계산처럼 큰 정수가 필요한 자리에서 BigInt는 확실한 도구가 된다. 다만 BigInt와 일반 Number를 그냥 섞어서 더하면 에러가 난다. 한쪽으로 형을 맞춰야 한다는 점만 기억하면 된다.
문자열과 템플릿 리터럴
문자열은 텍스트 데이터다. 작은따옴표, 큰따옴표로 감싸고, ES6부터는 백틱(` “)으로 감싼 템플릿 리터럴을 쓴다. 템플릿 리터럴은 변수 삽입과 여러 줄 처리가 깔끔해서 사실상 표준처럼 자리 잡았다.
const word = '추측';
// 기존 방식
const before = word + '에 의존하는 것은 그의 투자 철학에 배치된다.';
// 템플릿 리터럴
const after = `${word}에 의존하는 것은 그의 투자 철학에 배치된다.`;
문자열 안에 같은 따옴표를 넣어야 하면 역슬래시로 이스케이프하거나, 바깥 따옴표를 다른 종류로 쓰면 된다. \n(줄 바꿈), \t(탭), \uXXXX(유니코드) 같은 이스케이프 문자도 여기서 함께 쓰인다.
null과 undefined, 그리고 typeof의 함정
null과 undefined는 둘 다 “값이 없음”을 뜻한다. 그래서 조건문에서는 거짓처럼 동작한다. 둘을 어떻게 나눠 쓰냐고? 관례는 단순하다. undefined는 시스템이 “아직 값을 안 넣었다”고 표시할 때 쓰는 값이고, null은 개발자가 “여기는 일부러 비워뒀다”고 직접 넣는 값이다. 그러니 빈 값을 의도적으로 할당할 때는 null을 쓰는 편이 의도가 분명하다.
let box; // 선언만, 값은 undefined
let cleared = null; // 비어 있음을 개발자가 명시
여기서 자바스크립트의 유명한 함정이 하나 있다. typeof로 타입을 확인할 때 null만 엉뚱하게 나온다.
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (버그처럼 보이는 표준 동작)
typeof null이 “object”로 나오는 건 초창기 설계의 흔적인데, 호환성 때문에 지금도 고치지 않는다. 그래서 null을 검사할 때는 typeof가 아니라 === null로 직접 비교해야 한다. 이 한 줄을 모르면 조건 분기에서 한참을 헤맨다.
심볼과 객체, 그리고 배열
심볼은 ES6에서 들어온, 항상 유일한 값이다. 같은 설명을 붙여 만들어도 서로 다르다. 그래서 다른 코드와 충돌하지 않는 객체 프로퍼티 키로 자주 쓰인다.
const a = Symbol('investor');
const b = Symbol('investor');
console.log(a === b); // false (언제나 유일하다)
객체는 이름과 값으로 이뤄진 프로퍼티의 묶음이다. 점 표기법이나 대괄호로 접근하고, 함수도 담을 수 있다. 객체를 제대로 다루는 사고방식이 궁금하면 자바스크립트는 정말 객체지향 프로그래밍 언어일까요에서 더 깊게 이어갈 수 있다.
배열은 인덱스를 키로 갖는 특수한 객체다. 순서를 보장하고, 길이가 고정되지 않으며, 서로 다른 타입을 한 배열에 담을 수 있다. Map과 Set도 ES6에서 합류했는데, Map은 객체보다 유연한 키-값 저장소, Set은 중복을 허용하지 않는 모음이다. Set은 최근 교집합·합집합 같은 메서드까지 표준에 들어와 쓰임이 더 넓어졌다. 이 변화는 자바스크립트의 Set 객체와 새로운 메서드들에 정리돼 있다.
날짜는 이제 Temporal로 넘어간다
오랫동안 날짜는 내장 Date 객체로 다뤘다. new Date()로 현재 시각을 만들고, 메서드로 연·월·일을 꺼낸다.
const now = new Date();
console.log(now.getFullYear()); // 연도
console.log(now.getMonth()); // 월 (0부터 시작! 주의)
여기서도 함정이 있다. 월의 인덱스가 0부터 시작한다. 1월이 0이다. 이런 헷갈리는 설계와 가변성, 시간대 처리의 약점 때문에 Date는 오래 비판받았다. 그 대안이 드디어 표준이 됐다. Temporal API다. 9년의 논의 끝에 2025년 TC39 Stage 4에 도달해 ES2026 명세에 들어가고, TC39 proposal-temporal 저장소 기준으로 이미 정식 제안으로 확정됐다. 브라우저 탑재도 시작됐다. Firefox 139가 2025년 5월, Chrome 144가 2026년 1월에 실었다.
Temporal은 불변 타입에 시간대와 달력을 1급으로 다루고, 나노초 정밀도까지 지원한다. 다만 Date가 사라지는 건 아니니 기존 코드를 당장 갈아엎을 필요는 없다. 새 코드부터 천천히 옮기면 된다. 무엇이 어떻게 달라지는지는 날짜 처리를 혁신시키는 자바스크립트의 Temporal API와 MDN의 Temporal 문서에서 확인할 수 있다.
타입 변환에서 사고가 나는 지점
타입 변환은 매일 하는 작업이다. 사용자 입력은 보통 문자열로 들어오니까, 숫자가 필요하면 바꿔야 한다. 그런데 자바스크립트가 알아서 바꿔주는 자동 변환이 사고의 출발점이다.
const multiply = 1 * '100'; // 100 (문자열이 숫자로 변환됨)
const plus = 1 + '100'; // "1100" (숫자가 문자열로 변환됨)
곱하기는 문자열을 숫자로 바꿔 계산하고, 더하기는 숫자를 문자열로 바꿔 이어붙인다. 같은 값인데 연산자에 따라 결과가 갈린다. 그래서 가장 안전한 습관은 숫자가 필요하면 명시적으로 변환하는 것이다.
const n1 = Number('123'); // 123
const n2 = parseInt('16Hz', 10); // 16 (읽을 수 있는 데까지만)
const n3 = parseFloat('15.5km'); // 15.5
const fail = Number('abc'); // NaN (변환 실패)
parseInt와 parseFloat은 숫자로 읽히는 부분까지만 변환하고 나머지는 버린다. 변환이 불가능하면 NaN이 나오니, Number.isNaN()으로 결과를 점검하는 습관이 좋다. 반대로 문자열로 바꿀 때는 String(value)나 toString()을 쓰고, 불리언으로 바꿀 때는 Boolean(value) 또는 !!value를 쓴다. 숫자 0, 빈 문자열, null, undefined, NaN은 모두 거짓 같은 값이라는 점만 기억하면 헷갈릴 일이 없다.
console.log(Boolean(0)); // false
console.log(Boolean('false')); // true ('false'는 빈 문자열이 아니다)
console.log(!!''); // false
문자열 'false'가 참이 된다는 점은 특히 자주 걸린다. 문자열은 비어 있지 않으면 전부 참이다. 이런 작은 함정들을 모아 둔 자바스크립트 개발자들이 자주 만나는 10가지 문제와 해결책도 같이 보면 실수를 미리 줄일 수 있다.
정리
자바스크립트 데이터 타입은 원시 7개와 객체 하나로 정리된다. BigInt가 합류하면서 숫자의 한계를 넘는 길이 열렸고, Date의 빈자리는 Temporal이 채우는 중이다. 변하지 않는 핵심은 하나다. 숫자가 필요하면 숫자만, 문자열이 필요하면 문자열만 쓰고, 변환은 자동에 맡기지 말고 직접 하는 것. 타입을 정확히 알고 쓰면, 디버깅으로 날리던 시간이 그대로 돌아온다.
참고 자료
- MDN Web Docs, “JavaScript data types and data structures”
- TC39, “proposal-temporal (GitHub)”