javascript에서 undefined와 null은 값이다?
코딩을 하다 보면 자주 마주치는 `undefined`와 `null`.
이 둘의 차이에 대해서는 어느 정도 알고 있었다. `undefined`는 값이 할당되지 않았을 때 자바스크립트가 자동으로 부여하는 값이고, `null`은 개발자가 의도적으로 “값이 없다”는 것을 명시할 때 사용한다는 정도였다.
하지만 문득 이런 생각이 들었다
‘`undefined`와 `null`이 정말 “특정한 값”이라면, 자바스크립트 엔진은 어떻게 이들을 숫자나 문자열과 구별할 수 있을까?’
이러한 궁금증을 시작으로 자바스크립트의 내부 동작 방식을 파헤쳐보았다.
undefined와 null은 정말 ‘값’일까?
결론부터 말하자면 undefined와 null 모두 자바스크립트에서 명확한 ‘특정한 값’이다.
let a;
console.log(a); // undefined
let b = null;
console.log(b); // null
console.log(typeof a); // "undefined"
console.log(typeof b); // "object"
실제로 콘솔에 출력되고, 비교 연산도 가능하다. 즉, 이들은 단순히 “비어있음”이 아니라 자바스크립트에서 정의된 실제 값인 것이다.
그런데 여기서 흥미로운 점을 발견했다. `null`의 `typeof` 결과가 `"object"`라는 것이다.
왜 null의 타입이 object일까?
이는 자바스크립트 초기 구현의 역사적인 버그다.
자바스크립트가 처음 개발될 때, 값들은 내부적으로 “타입 태그(type tag)“와 “실제 값”으로 저장되었다. 당시 객체와 null 모두 타입 태그 값이 0으로 설정되어 있었다.
그 결과 `typeof` 연산자가 null을 검사할 때도 “이 값의 타입 태그가 0이니까 객체구나!“라고 잘못 판단하게 된 것이다.
이 버그는 이미 수많은 코드베이스에 의존성이 생겨버려서, 현재까지도 의도적으로 수정하지 않고 있다.
타입 태그란 무엇인가?
그럼 여기서 궁금증이 하나 더 생겼다.
‘모든 값이 타입 태그를 가지고 있다는 건가? 그럼 이 태그는 어떻게 저장되는 거지?’
조사해보니 자바스크립트 엔진은 값과 타입 정보를 효율적으로 저장하기 위해 다양한 최적화 기법을 사용한다는 것을 알게 되었다.
예를 들어, 64비트 공간에서:
• 상위 비트에는 “이 값이 어떤 타입인지”를 나타내는 태그
• 하위 비트에는 실제 값
이렇게 저장하는 Tagged Pointer 기법을 사용한다.
NaN-boxing이라는 신기한 기법
더 흥미로운 건 NaN-boxing이라는 기법이었다.
자바스크립트에서 `NaN`(Not a Number)은 “숫자가 아님”을 나타내는 특별한 값이다.
console.log(0 / 0); // NaN
console.log("abc" * 5); // NaN
console.log(typeof NaN); // "number" (!)
`NaN`의 특징은 다음과 같다:
• 자기 자신과도 같지 않다 (`NaN !== NaN`)
• 타입은 “number”다
• 어떤 연산이든 NaN이 포함되면 결과도 NaN이 된다
그런데 IEEE 754 표준에서 `NaN`은 수많은 서로 다른 비트 패턴으로 표현될 수 있다. 이 중 실제로 “숫자 아님”을 나타내는 데는 하나만 있으면 되므로, 남는 비트 공간을 활용해서 다른 타입의 값들을 저장하는 것이 바로 NaN-boxing이다.
NaN-boxing의 실제 동작 방식
간단히 말하면 이렇다:
실수가 아닌 값들(정수, 불리언, 포인터, null, undefined 등)을 저장할 때:
1. NaN 영역임을 나타내는 비트 패턴 (상위)
2. 타입 태그 (이 값이 정수인지, 불리언인지, null인지 등)
3. 실제 값 (하위)
이 세 가지 정보를 64비트 한 공간에 모두 담는다.
실수 값: [IEEE 754 double 형식 그대로]
다른 값: [NaN 패턴][타입 태그][실제 값]
이렇게 하면 모든 값의 크기가 동일해져서 메모리 효율성도 높아지고, 타입 판별도 빠르게 할 수 있다.
객체는 어떻게 저장될까?
그럼 객체는 어떨까?
객체의 경우 조금 다르다. 객체 자체는 힙(heap) 메모리에 저장되고, 변수에는 그 객체를 가리키는 **메모리 주소(참조값)**가 저장된다.
하지만 이 참조값 역시 NaN-boxing이 적용된 환경에서는:
• NaN 패턴 표시
• “이건 객체 참조야”라는 타입 태그
• 실제 힙의 주소
이렇게 64비트에 함께 저장된다.
결국 모든 값이 태그로 구별된다
정리하면:
• `null`, `undefined`도 각각의 고유한 타입 태그를 가진다
• 자바스크립트 엔진은 이 태그를 통해 값의 종류를 빠르게 판별한다
• `typeof` 연산자나 `===` 비교 등이 모두 이 내부 구조를 기반으로 동작한다
이는 마치 도서관에서 책마다 분류 번호를 붙여서 관리하는 것과 비슷하다. 겉으로 보기에는 그냥 “값”이지만, 내부적으로는 모든 값에 “나는 이런 종류의 값이야”라는 태그가 붙어있는 것이다.