Type Judgment
In JavaScript, there are 7 built-in types, which are:
- Null (
null
) - Undefined (
undefined
) - Boolean (
boolean
) - Number (
number
) - String (
string
) - Object (
object
) - Symbol (
symbol
)
Strictly speaking, JavaScript has 6 primitive types (value types):
- Null (
null
) - Undefined (
undefined
) - Boolean (
boolean
) - Number (
number
) - String (
string
) - Symbol (
symbol
)
In addition, there is a special primitive type:
- BigInt, used to represent integers larger than the range of
Number
.
These primitive types are immutable, while the Object
type is a complex type that can contain multiple values.
What is typeof
typeof
is an operator in JavaScript used to check the type of a variable. It returns a string that indicates the type of the given value.
Next, we will use the typeof
operator to see the type of values; it returns the string value of the type. Interestingly, these seven types and their string values do not correspond one-to-one:
console.log(typeof 777); // number
console.log(typeof 3.14); // number
console.log(typeof 0); // number
console.log(typeof Infinity); // number
console.log(typeof Number('moment')); // number
console.log(typeof 77n); // bigint
console.log(typeof '1'); // string
console.log(typeof typeof 1); // string typeof returns a string
console.log(typeof String(777)); // string
console.log(typeof true); // boolean
console.log(typeof false); // boolean
console.log(typeof Boolean(5)); // boolean // Boolean() converts based on whether the argument is truthy or falsy
typeof !!1 === 'boolean'; // Calling ! (logical NOT) twice is equivalent to Boolean()
console.log(typeof Symbol()); // symbol
console.log(typeof Symbol('foo')); // symbol
console.log(typeof Symbol.iterator); // symbol
console.log(typeof { a: 1 }); // object
console.log(typeof [1, 2, 4]); // object
console.log(typeof new Date()); // object
console.log(typeof /regex/); // object
console.log(typeof null); // object
console.log(typeof function () {}); // function
console.log(typeof class T {}); // function
You may have noticed that the null
type is quite special, typeof null === 'object'
, the correct return should be 'null'
, but this bug has existed for nearly twenty years in JavaScript and may never be fixed.
In the original implementation of JavaScript, values in JavaScript were represented by a type label and the actual data value. The type label for objects is 0. Since null
represents a null pointer (most platforms have a value of 0x00), the type label for null
is also 0, which is why typeof null
returns "object"
.
console.log(typeof function () {}); // function
In the code above, the output is 'function'
, indicating that function
is also a built-in type in JavaScript. However, in the specification, it is actually a subtype of object
. Specifically, functions are callable objects
, which have an internal property [[call]]
that allows them to be called.
function f() {}
console.log(f.__proto__.constructor === Function); // true
console.log(f.__proto__.__proto__.constructor === Object); // true
// Functions are not only objects but can also have properties
console.log(f.name); // f
console.log(f.arguments); // null because no parameters were passed
By using the prototype method to determine, functions are indeed subclasses of Object.
Now let’s look at arrays. JavaScript supports arrays, and arrays are also objects. More precisely, they are also a subtype of object
, and the elements of an array are indexed in numerical order, with its length
property being the number of elements.
const foo = [];
console.log(foo.__proto__.constructor === Array); // true
console.log(foo.__proto__.__proto__.constructor === Object); // true
The new Operator
- All constructor functions called with
new
will return non-primitive types; the return is eitherobject
type orfunction
. Most return objects, but a notable exception isFunction
, which returns a function.
const str = new String('777');
const num = new Number(777);
const func = new Function();
console.log(typeof str); // object
console.log(typeof num); // object
console.log(typeof func); // function
Undefined and Undeclared
- A variable is
undefined
when it does not hold a value. At this point,typeof
returnsundefined
:
var a;
console.log(tyoeof a); // undefined
Most developers tend to equate undefined
with undeclared
, but in JavaScript, they are completely different. A variable declared in the scope but not yet assigned a value is undefined
. In contrast, a variable that has not been declared in the scope is undeclared
.
In the example above, bar is not defined
can easily be mistaken for bar is undefined
. However, undefined
and is undefined
are two different things, but typeof
handles undeclared
and returns undefined
, for example:
var foo;
console.log(typeof foo); // undefined
console.log(typeof bar); // undefined
Both return "undefined"
as is, and typeof bar
does not throw an error because typeof
has a special safety mechanism.
Internal Property [[class]]
In the previous examples, using typeof
for judgment, whether it is null
, Object
, Array
, etc., all return "object"
. So is there a mechanism to determine what specific type of value it is? The answer is yes.
All objects for which typeof
returns object
(such as arrays) contain an internal property [[class]]
, which we can think of as an internal classification, rather than a class in the traditional object-oriented sense. This property cannot be accessed directly and is generally viewed through Object.prototype.toString(...)
. For example:
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('moment')); //[object String]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(function f() {})); // [object Function]
console.log(Object.prototype.toString.call(class C {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call(new Boolean(1))); // [object Boolean]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]
In the example above, the internal [[class]]
property value of an array is Array
, and the value of a regular expression is RegExp
. In most cases, the internal [[class]]
property of an object corresponds to the built-in native constructor that created the object, but this is not always the case. For example, some primitive types, such as null
and undefined
, do not have native constructors like Null()
and undefined()
, but their internal [[class]]
property values are still Null
and Undefined
.
Other primitive types, such as strings, numbers, and booleans, behave differently. Since primitive type values do not have properties and methods like .length
and .toString()
, they need to be wrapped in objects to access them. At this point, JavaScript automatically wraps primitive types into an object. For example, var foo = 'moment';
actually performs var foo = new String('moment');
, turning it into an object that has its own properties and methods. If you want to get the primitive type value from the wrapped object, you can use the valueOf()
function, for example:
var foo = new String('moment');
console.log(foo); // [String: 'moment']
console.log(foo.valueOf()); // moment
console.log(typeof foo.valueOf()); // string
Custom typeof
typeof
is very useful, but it is not as universal as needed. For example, typeof []
is "object"
, as well as typeof new Date()
and typeof /abc/
, etc.
To explicitly check the type, MDN provides a custom type(value)
function that mainly mimics the behavior of typeof
, but for non-primitive types (i.e., objects and functions), it returns more detailed type names whenever possible.
function type(value) {
// If the passed value is null, return null
if (value === null) {
return 'null';
}
const baseType = typeof value;
// If it is a primitive type
if (!['object', 'function'].includes(baseType)) {
return baseType;
}
// Symbol.toStringTag usually specifies the "display name" of the object's class
const tag = value[Symbol.toStringTag];
if (typeof tag === 'string') {
return tag;
}
// If it is a function, and its source code starts with the class keyword
if (baseType === 'function' && Function.prototype.toString.call(value).startsWith('class')) {
return 'class';
}
// The name of the constructor; for example, `Array`, `GeneratorFunction`, `Number`, `String`, `Boolean`, or `MyCustomClass`
const className = value.constructor.name;
if (typeof className === 'string' && className !== '') {
return className;
}
// If there is no suitable method to get the type of the value, return directly
return baseType;
}
References
-
Book: “You Don’t Know JS”
Summary
The typeof
operator is used to check the type of a variable, returning a string that represents the data type. For primitive types (such as number
, string
, etc.), the return value is clear, but for object types (such as null
, arrays, regular expressions, etc.), typeof
returns "object"
, leading to some special cases. To solve this ambiguity, you can use Object.prototype.toString.call()
to accurately determine the actual type of an object. In addition, typeof
cannot determine arrays, null
, and classes, requiring additional handling.