Why typeof(null) is "object" in JavaScript?
A brief history about the "necessary evil"
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?
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 fornull
, It examined its type tag and the type tag said โobjectโ and it went inside theelse 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 toVOID
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!