Comparing Type Checks in JavaScript
As an untyped language, JavaScript necessitates dynamic type checking. To strictly enforce type for every variable would hamper much of the language’s power, but when necessary, understanding the different ways to check type will make our programs better. There are many ways to type check in JavaScript, and I will go over four1 in this post, discussing nuances of each.
typeof
operator
The typeof
operator in JavaScript returns a string describing the type of
any value its given. Its implementation is standard and used widely, defined
in the first edition of ECMAScript2. It has six possible string values in
ECMAScript 53.
typeof 'str'; // "string"
typeof 10; // "number"
typeof true; // "boolean"
typeof {}; // "object"
typeof function(){}; // "function"
typeof undefined; // "undefined"
The consistency and reliability of this operator is terrific, but we run into
problems for anything that isn’t one of the above types, including arrays,
null
, and custom objects. Furthermore, type checking this way requires that
we compare strings, which is sensitive to case and spelling (and generally
gives me the heebie jeebies).
typeof null; // "object"
typeof []; // "object"
typeof new String('str'); // "object"
typeof new function Custom(){}; // "object"
typeof {} === 'Object'; // false
typeof {} === 'objcet'; // false
instanceof
operator
The instanceof
operator compares two values—a variable and a function—by
checking the variable’s prototype chain for the function object’s prototype.
Because we can provide any function we want as the second parameter,
instanceof
can be used check custom types. Furthermore, instanceof
uses a
function instead of a string, so our program will let us know if we’ve made
any mistakes.
{} instanceof Object; // true
[] instanceof Array; // true
(function(){}) instanceof Function; // true
var Foo = function(){};
new Foo() instanceof Foo; // true
new Foo() instanceof Doo; // ReferenceError: Doo is not defined
Checking an object’s prototype chain is very helpful, but instanceof
will
always return false for primitive types (strings, numbers, booleans, null
,
and undefined
) because they are not objects and thus have no prototype.
'str' instanceof String; // false
9 instanceof Number; // false
true instanceof Boolean; // false
null instanceof Object; // false
undefined instanceof Object; // false
Although we cannot pass null
or undefined
as right-hand values (since they
aren’t functions), we can easily check for null
and undefined
with an
equivalence comparison.
undefined instanceof undefined; // TypeError: Expecting a function in instanceof check, but got undefined
null instanceof null; // TypeError: Expecting a function in instanceof check, but got null
undefined === undefined; // true
null === null; // true
constructor
property
Objects inherit the constructor
property from their prototype, which we can
use to check a variable’s type without going down the whole prototype chain.
Like instanceof
, we are checking with function objects, so we can check for
custom functions.
({}).constructor === Object; // true
/regex/.constructor === RegExp; // true
(new Date()).constructor === Date; // true
[].constructor === Array; // true
[] instanceof Array; // true
[].constructor === Object; // false
[] instanceof Object; // true
var Custom = function(){};
(new Custom()).constructor === Custom; // true
This also works for primitive numbers, strings, and booleans, because
JavaScript will wrap these in Number
, String
, and Boolean
when we
attempt to access a property on them.
(9).constructor === Number; // true
'str'.constructor === String; // true
true.constructor === Boolean; // true
However, since this method accesses a property instead of using an operator,
it is susceptible to a few problems. For example, it’s quite easy to change
the value of the property. We will also throw an error by accessing a property
on null
or undefined
. It’s easy to check equivalence for null
and
undefined
, but there isn’t a good way to check a changed property value.
var Custom = function(){};
var custom = new Custom();
custom.constructor = null;
custom.constructor === Custom; // false
custom = null;
custom.constructor === Custom; // TypeError: Cannot read property 'constructor' of null
custom === null; // true
Object.prototype.toString
method
Despite the wordiness of this approach, it avoids many
problems of the above methods. Object.prototype.toString
4 returns a string
in the form "[object Type]"
, which can be compared similarly to typeof
. We
must call it the long way, instead of simply toString
, because the method
may be overridden along the prototype chain. Unfortunately, we must compare
with strings, and custom objects will all return "[object Object]"
.
[1,2,3].toString(); // "1,2,3"
Object.prototype.toString.call([1,2,3]); // "[object Array]"
Object.prototype.toString.call(new Date())
.slice(8, -1) === 'Date'; // true
Object.prototype.toString.call(
new (function Custom(){})); // "[object Object]"
As with constructor
, this method wraps primitive types in their object
equivalent. As a bonus, it will return successfully when passed in null
or
undefined
.
Object.prototype.toString.call('str'); // "[object String]"
Object.prototype.toString.call(10); // "[object Number]"
Object.prototype.toString.call(false); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Conclusions
I would be presumptuous to claim there is one best way to check types in JavaScript. In truth, there are many valid ways, each with their pros and cons, and each better suited for specific situations and projects. Having a better understanding of these options gives us power to make an informed decision that’s best for our code. For convenience, here is a table summarizing a few properties of each approach.
typeof |
instanceof |
constructor |
toString |
|
---|---|---|---|---|
avoids string comparison | ✓ | ✓ | ||
commonly used | ✓ | ✓ | ||
checks custom classes | ✓ | ✓ | ||
directly check null | ✓ | |||
directly check undefined | ✓ | ✓ | ||
works across windows5 | ✓ | ✓ |
-
Additional approaches include duck testing (assuming type based on characteristics), specific methods like
Array.isArray
orNumber.isNaN
, and other comparative methods such asObject.prototype.isPrototypeOf
. There are probably others I forget here. ↩ -
ECMAScript: A general purpose, cross-platform programming language, §11.4.3, pp. 39–40. ↩
-
ECMAScript 6 provides the additional type
symbol
. The specification also introduces additional type checking methods, such asNumber.isInteger
. ↩ -
A slightly abbreviated version of this is
({}).toString.call
. In some browsers, you may also callwindow.toString.call
ortoString.call
, but results may vary. For example, while writing this post,window.toString.call(null)
returns"[object Window]"
in my version of Chrome and"[object Null]"
in my version Firefox. ↩ -
Every window/frame has unique instances for each built-in object. If we compare
Array
in one window withArray
from another, they will differ. This happens rare enough in my experience that I didn’t include any examples. Read more about it in this post by Douglas Crockford. ↩