Why typeof(null) is "object" in JavaScript?

A brief history about the "necessary evil"

ยท

6 min read

Context๐Ÿ‘€

If you are a developer dealing with JavaScript on a daily basis then it is very likely to assume that you have at least heard about the typeof operator. As we know the syntax of the typeof operator is followed by it's operand which is an expression representing the object or primitive whose type is to be returned.

Following that definition, typeof(ANY_PRIMITIVE_TYPE) should return that primitive type as a string and anything else apart from the primitive types should be treated as type "object".

As of date, JavaScript has 7 primitive data types available in the language:

Let us run the following code in console and see what each of them returns for Primitive types:

typeof "Sourav"    // "string"
typeof 1    // "number"
typeof 1n    // "bigint"
typeof true    // "boolean"
typeof undefined    // "undefined"
typeof Symbol('Debnath')    // "symbol"
typeof null    // "object"

Wait a minute....what?? Why null is of type "object" when it should be of type "null"? What happened to the definition which we were considering 'holy' and as the single source of truth?

typeof-5 (2).jpg

Well, this is what this blog is all about! We will explore the reason together, let us begin!



Why the null did you even do that? ๐Ÿคทโ€โ™‚๏ธ

Obviously, this might be the first question which most of you would have been thinking!

Did the JavaScript God (Yes, Brendan Eich ๐Ÿ™) himself did this intentionally? Well of course not, we couldn't have asked more given the time period he had in hand (10 days) to design a new language!

When you perform tasks or deliver your deliverables in a hurry, it is expected that your code might be shipped with bug(s). In modern days, we follow a model for Software Development where we have a dedicated phase for QA which was not the case in 1995-1996 in Netscape Communications. It was not called JavaScript either, it was LiveScript - that's a discussion for another day!.



The source code of the very early version of JavaScript Engine and introduction of the "necessary evil" ๐Ÿ˜ˆ

In the early version, values were stored in 32-bit units, which consisted of a small type tag (1โ€“3 bits) and the actual data of the value. The type tags were stored in the lower bits of the units. There were five of them:

  • 000: object. The data is a reference to an object.
  • 1: int. The data is a 31 bit signed integer.
  • 010: double. The data is a reference to a double floating point number.
  • 100: string. The data is a reference to a string.
  • 110: boolean. The data is a boolean.

That is, the lowest bit was either one, then the type tag was only one bit long. Or it was zero, then the type tag was three bits in length, providing two additional bits, for four types.

There was a special value:

null (JSVAL_NULL) was the machine code NULL pointer. Or: an object type tag plus a reference that is zero. The NULL pointer is represented as 0x00 in most of the platforms.

Let's look at the classic JavaScript Source code used in the '96 in SpiderMonkey courtesy: @evilpies

    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) // (3,4)
                 : ops->call != 0)) {  // (3)
                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;
    }
  • The first check here (1) in this code is a VOID (undefined) check which is performed by a C macro:
#define JSVAL_IS_VOID(v)  ((v) == JSVAL_VOID)
  • The next check (2) is whether the value has an object tag. Now for null, It examined its type tag and the type tag said โ€œobjectโ€ and it went inside the else if.

  • If it additionally is either callable (3) or its internal property [[Class]] marks it as a function (4) then v is a function. Otherwise, it is an object. This is the result that is produced by typeof null.

  • On top of that, we do not even have a separate check for type null, which was easy to implement by a C macro similar to VOID but as I described above,

When you perform tasks or deliver your deliverables in a hurry, it is expected that your code might be shipped with bug(s).



It is just another else if, let's fix that ๐Ÿš’

You can, but you should not!

A proposal was given In ES5.1 to fix the typeof operator and it was implemented under the harmony version in V8 but it ended up breaking a lot of existing sites and this proposal had been rejected, described as,

In the spirit of One JavaScript this is not feasible.



What's present today under the hood ๐Ÿ•ต๏ธ

Things have moved to CPP and the code present today is pretty straight forward with the known bug of typeof null; as Mr. JavaScript said in one of the proposal discussions,

"I think it is too late to fix typeof. The change proposed for typeof null will break existing code."

Let's look at the implementation of typeof in the CPP Interpreter in Mozilla from searchfox:

JSType js::TypeOfValue(const Value& v) {
  switch (v.type()) {
    case ValueType::Double:
    case ValueType::Int32:
      return JSTYPE_NUMBER;
    case ValueType::String:
      return JSTYPE_STRING;
    case ValueType::Null: // ๐Ÿ˜ˆ ๐Ÿ˜ˆ ๐Ÿ˜ˆ
      return JSTYPE_OBJECT; // ๐Ÿ˜ˆ ๐Ÿ˜ˆ ๐Ÿ˜ˆ
    case ValueType::Undefined:
      return JSTYPE_UNDEFINED;
    case ValueType::Object:
      return TypeOfObject(&v.toObject());
    case ValueType::Boolean:
      return JSTYPE_BOOLEAN;
    case ValueType::BigInt:
      return JSTYPE_BIGINT;
    case ValueType::Symbol:
      return JSTYPE_SYMBOL;
    case ValueType::Magic:
    case ValueType::PrivateGCThing:
      break;
  }

  ReportBadValueTypeAndCrash(v);
}

Where JSTYPE_OBJECT is a cpp Enum which could be observed here :

/* Result of typeof operator enumeration. */
enum JSType {
  JSTYPE_UNDEFINED, /* undefined */
  JSTYPE_OBJECT,    /* object */
  JSTYPE_FUNCTION,  /* function */
  JSTYPE_STRING,    /* string */
  JSTYPE_NUMBER,    /* number */
  JSTYPE_BOOLEAN,   /* boolean */
  JSTYPE_NULL,      /* null */
  JSTYPE_SYMBOL,    /* symbol */
  JSTYPE_BIGINT,    /* BigInt */
  JSTYPE_LIMIT
};



CONCLUSION โœ๏ธ

Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below." - Master Yuan-Ma, The Book of Programming

We deal with a lot of stuff daily while developing a feature or a product but we might not actually explore those lines we write in detail to find why something happens. This blog is just a theoretical exploration of one such kind๐Ÿ˜‡

Now, after all the C and CPP, let's bring our thoughts down to JS! You might ask, isnโ€™t typeof([]) === "object" a bug?

The answer is: No, it is not a bug.

Arrays arenโ€™t primitive, so they are objects! So are dates, and everything not on the "primitive list".

Unlike null, theyโ€™re telling the truth!

ย