null은 버그다?

Lellow_Mellow·2023년 4월 12일
3

JavaScript

목록 보기
1/4
post-thumbnail

블로깅 시작 전에

프론트엔드 웹 개발자를 희망한다면 당연히 익숙해져야 할 언어 중 하나는 JavaScript라고 생각합니다. 다만, JavaScript를 사용하거나 공부하면서 모호한 개념이나 헷갈리는 부분들, 궁금증이 생기는 부분들이 다수 존재했습니다.

이러한 부분들에 있어 더 깊이 있는 학습과 더불어 지식을 공유하고자 글을 작성하게 되었습니다. 앞으로 해당 시리즈의 포스팅 역시 동일한 목적을 가지고 작성할 예정입니다.


이번 포스팅의 목표

이번 포스팅은 JavaScript에서의 undefinednull의 정확한 차이에 대해서 정리하는 것을 목표로 하고 있습니다. 아무렇지 않게 사용해왔던 값들에 대해 다시금 복습하고 넘어갈 수 있는 계기가 되었으면 좋겠습니다.


null? undefined?

nullundefinedJavaScript에서의 원시 값에 해당됩니다. 이 둘은 없음 을 나타내는 값으로 사용됩니다. 다만, 이 둘 사이에는 분명한 차이가 존재합니다.


원시 값?

우선 원시 값이 어떤 것인지에 대해 알아보고 넘어가겠습니다. JavaScript에서의 원시 값(primitive, 원시 자료형)은 객체가 아니면서 메서드도 가지지 않는 데이터를 의미합니다. 이러한 원시 값은 7종류가 존재합니다.

string, number, bigint, boolean, undefined, symbol, null

좀 더 쉽게 말하자면, 원시 값은 JavaScript에서 사용할 수 있는 데이터 및 정보의 가장 단순한 형태라고 할 수 있습니다. 예를 들어보자면, 숫자나 문자같은 값들은 더 이상 단순화할 수 없기 때문에 이들을 원시 값이라 부르는 것입니다.

이러한 원시 값은 불변값이므로 변형할 수 없습니다. 원시 값에 대한 내용은 다른 포스팅에서 더 자세하게 다루어보도록 하겠습니다.


undefined

먼저 undefined에 대해서 알아보겠습니다.

undefined는 말 그대로 정의되지 않았음을 의미합니다. undefined는 사용자가 명시적으로 지정이 가능한 값이며, JavaScript 엔진이 자동으로 부여하는 값입니다. JavaScript 엔진undefined를 부여하는 경우는 아래와 같습니다.

  • 값을 대입하지 않은 변수, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에게 접근하는 경우
  • 객체 내부의 존재하지 않는 프로퍼티에 접근하는 경우
  • return 문이 없거나 호출되지 않는 함수를 실행할 경우

정리하자면, 존재하지 않거나 정의되지 않은 값에 접근하려고 하는 경우에 해당한다고 볼 수 있습니다.

JavaScript 엔진에서는 변수 선언이 2단계로 이루어집니다.

1. 선언 단계

선언 단계에서는 실행 컨텍스트의 변수 객체에 변수를 등록합니다. 다시 말해 스코프에 변수를 등록하는 단계이며 이 단계에서 호이스팅이 일어납니다.

실행 컨텍스트? 스코프? 호이스팅?

해당 개념들에 대해 간단하게만 이해하고 넘어가겠습니다. 이에 대한 자세한 내용은 이후 포스팅에 추가될 예정입니다.

  • 실행 컨텍스트 : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
  • 스코프 : 변수에 접근할 수 있는 범위
  • 호이스팅 : 변수와 함수 선언이 스코프의 최상단으로 끌어 올려진 것 같은 현상을 의미

2. 초기화

앞서 선언 단계에서 실행 컨텍스트에 등록한 변수를 위한 메모리를 만드는 단계입니다. 바로 이 단계에서 JavaScript 엔진은 암묵적으로 undefined를 할당하여 초기화합니다.

이후 변수는 할당 과정을 거치게 됩니다.


null

null 역시 undefined와 유사한 의미를 가지고 있습니다. 다만, null어떤 값이 의도적(명시적)으로 비어있음을 표현하기 위해 사용됩니다.

undefined 대신 null을 사용해야 할까요? 이는 아래 예제를 보면서 확인해보겠습니다.

let foo; // 값을 대입한 적 없음
let bar = undefined; // 값을 대입함
foo; // undefined
bar; // undefined (??)
let obj1 = {}; // 속성을 지정하지 않음
let obj2 = {prop: undefined}; // 속성을 지정함
obj1.prop; // undefined
obj2.prop; // undefined (??)

다음과 같이 비어있음을 명시하기 위해 undefined를 사용해보았습니다. 하지만, 이 경우에는 foobar / obj1obj2 사이의 차이가 불분명해집니다. 해당 값이 대입한 적이 없어 undefined인지, 없음을 나타내기 위해 undefined로 할당한 것인지 구분할 수 없습니다.

그렇기에 undefined를 직접 명시하는 방법 대신에 null을 사용해 비어있음을 명시적으로 표현하게 됩니다.


null은 객체다?

undefined의 경우, 해당 type을 출력해보면 undefined입니다. 그렇다면 null도 type이 null일까요?

console.log(typeof undefined); // undefined
console.log(typeof null); // object

어라? 원시값이므로 당연히 null이 출력될 줄 알았으나, 객체를 의미하는 object가 출력되었습니다. 이게 어떻게 된 일인지 알아보겠습니다.

null은 JavaScript의 버그다?

놀랍게도, null의 type이 객체인 것은 고치지 못한 버그였습니다.

JavaScript를 처음 구현할 당시에 JavaScript의 값은 type을 의미하는 type 태그와 값으로 이루어져 있었습니다. 객체의 type 태그는 0이었으며, nullNull pointer(0x00)표현되었습니다.

정확히는 32비트 단위로 이루어져 있었으며, 이 중에 단위의 하위 1~3비트가 type 태그로 사용되었습니다. nullNull pointer로 표현되었기 때문에 이러한 판단 기준에 의해 typeof에서 객체로 판별된 것입니다. 해당 코드를 살펴보겠습니다.

JS_PUBLIC_API(JSType)
    JS_TypeOfValue(JSContext *cx, jsval v)
    {
        JSType type = JSTYPE_VOID;
        JSObject *obj;
        JSObjectOps *ops;
        JSClass *clasp;

        CHECK_REQUEST(cx);
        if (JSVAL_IS_VOID(v)) {  // (1)
            type = JSTYPE_VOID;
        } else if (JSVAL_IS_OBJECT(v)) {  // (2)
            obj = JSVAL_TO_OBJECT(v);
            if (obj &&
                (ops = obj->map->ops,
                 ops == &js_ObjectOps
                 ? (clasp = OBJ_GET_CLASS(cx, obj),
                    clasp->call || clasp == &js_FunctionClass)
                 : ops->call != 0)) {
                type = JSTYPE_FUNCTION;
            } else {
                type = JSTYPE_OBJECT;
            }
        } else if (JSVAL_IS_NUMBER(v)) {
            type = JSTYPE_NUMBER;
        } else if (JSVAL_IS_STRING(v)) {
            type = JSTYPE_STRING;
        } else if (JSVAL_IS_BOOLEAN(v)) {
            type = JSTYPE_BOOLEAN;
        }
        return type;
    }

typeofundefined > object > number > string > boolean 순으로 type을 판단하였습니다. 이 중에서 object 단계에서 typeof null이 할당된 것입니다.

JavaScript는 이를 공식적인 버그로 인정했습니다. 해당 버그를 해결할 경우, 이미 존재하는 수많은 사이트들이 정상적으로 동작하지 않을 수 있기 때문이었습니다. 또한 하위 호환성을 위하여 null임을 판단하기 위해서는 typeof 대신에 x === null을 활용할 것을 권장하고 있습니다.


글을 마무리하며

언뜻 보면 쉽게 넘어갈 수 있는 nullundefined에 대해 관심이 생겨 이번 포스팅을 진행하게 되었습니다. 우리가 기억할 것은 다음과 같습니다.

null임을 판단하기 위해서는 일치 연산자 ===를 사용하자.
undefined 대신에 명시적으로 null을 사용하자.

부족한 글 읽어주셔서 감사드리며, 잘못된 부분이 존재하거나 궁금한 점이 있으시다면 댓글 남겨주시면 감사드리겠습니다. 😸


참고

profile
festina lenta

4개의 댓글

comment-user-thumbnail
2023년 4월 12일

좋은글 감사합니다 !

1개의 답글
comment-user-thumbnail
2023년 4월 13일

블로깅 방식 마음에 들어요 ㅎㅎ 열심히 하시는것 같아서 기분 좋네요 😎

1개의 답글