Eight types, and where typeof lies
Seven kinds of single value, one kind that lives by reference, and the three answers `typeof` gives that are not what they look like.
Every value in JavaScript has exactly one type, and the list of types is closed: there are eight and you cannot add a ninth. That is a small enough number to hold in your head, and holding it there explains a great deal of what comes later.
There is an operator that will tell you which one a value is. Start with the three you would guess.
Change 42 to -1.5, then to NaN, and watch the first answer stay the same.
typeof is an operator: a piece of built-in syntax that acts on whatever follows it. It gives back a short piece of text naming the type. Note that the answer is text, so typeof 42 is the six-character word "number", not the number type itself.
Now the four that are less obvious. Predict each line before you run it.
Change 10n to 10 and watch the third answer change. The n is what makes it a different type.
Three of those are correct and one is a lie. null is not an object, and the reason it says so is dealt with at the end of this lesson. First, the eight.
The list
| Type | A value of it | What it is for |
|---|---|---|
| undefined | undefined | No value was supplied |
| null | null | A value is absent on purpose |
| boolean | true, false | Two-way answers |
| number | 42, -1.5, NaN | All ordinary arithmetic, held as a 64 bit binary floating point number, the format C calls a double |
| bigint | 10n, 9007199254740993n | Whole numbers with no size limit |
| string | "hello" | Text, as a sequence of 16 bit units |
| symbol | Symbol("id") | A value guaranteed to differ from every other value |
| object | { a: 1 }, [1, 2], console.log | Anything made of parts |
The first seven are called primitives. A primitive is a single, indivisible value that cannot be altered. There is no operation anywhere in the language that changes the number 42 into something else, or edits the text "hello" in place.
That last claim sounds testable, so test it.
Change let to const and run it again. The result is identical, which tells you this has nothing to do with const.
The assignment was accepted and had no effect. Text looks as though it has parts you can reach, and reading s[0] does give you back "h", but that is a brand new one-character string rather than a piece of the old one. Writing to it changes nothing.
The eighth type is the odd one
An object is a bundle of named values. It is the only type that is not a primitive, and it behaves differently in a way that matters constantly.
Change const b = a; to const b = { count: 1 }; and run it again. Two objects that look the same are not the same object.
Nothing was written to a, and a.count changed anyway. b did not get a copy of the object; it got the same object. Names hold a reference to an object, which you can think of as its address rather than its contents.
Primitives do not work that way. If you copy a number into another name and change the second, the first is untouched, because there was never anything shared. The section on objects and references takes this apart properly; here it is enough to know that the split runs exactly along the primitive line.
Where typeof lies
typeof gives three answers that do not mean what they appear to.
Change neverDeclaredAnywhere to console.neverDeclaredAnywhere and run it again. The answer stays the same for a different reason.
- `typeof null` is `"object"`. In the first implementation, a value was tagged with a few bits saying what it was, and objects were tagged zero. A null pointer is also all zeros, so it was read as an object. It has been proposed and rejected as a fix more than once, because too much existing code checks for it.
nullis a primitive, and this answer is a 1995 bug preserved on purpose. - `typeof someFunction` is `"function"`. There is no function type in the list of eight, because functions are objects that happen to be callable.
typeofsingles them out because "can I call this?" is a question people ask far more often than "is this an object?". - `typeof somethingNeverDeclared` is `"undefined"`. Every other way of reading a name that was never declared stops the program dead.
typeofis the one exception in the whole language, which makes it the tool for asking whether something exists without risking a crash. The lesson on the temporal dead zone comes back to this.
There are eight types. typeof also gives back eight different strings. Why is that a coincidence rather than a match?
The eight answers are "undefined", "boolean", "number", "bigint", "string", "symbol", "object" and "function". Line them up against the eight types and two do not correspond. There is no "null" among the answers, because null reports as "object", so one type has no answer of its own. And "function" is among them although there is no function type, so one answer has no type of its own. One lost and one gained brings the count back to eight by accident. The undeclared-name case adds no ninth string: it reuses "undefined", which is what makes it impossible to tell from a name that exists and holds undefined.