Type Checking Techniques in JavaScript Part 2 of 2
To get the most out of this article you’ll want a solid grasp of JavaScript’s data types including built-in, custom, and host objects. If you need a little refresher see Part 1.
As front-end developers today are working with larger code bases, more 3rd party frameworks, and in teams, using effective type checking techniques is critical to writing bug free code. And you know how it goes, right? Less bugs means less Jira tickets reopened, less QA regressions, less work for project leads, and happier managers.
But the JavaScript language seems to be working against us. With it’s untyped var
keyword, absence of typed parameters in function signatures, and implicit type conversions, combined with the lack of a unified getType()
function, our need for reliable approaches is only increased.
In this article I’ll cover three effective techniques for type checking and identify each of their advantages and disadvantages. Let’s get started.
typeof operator #
This first type checking technique is useful for checking primitives types. The typeof
operator returns a string representation of the variable’s type[1].
The syntax is
typeof variable
In action
var val;
typeof val; // returns "undefined"
val = 10000;
typeof val; // returns "number"
val = 'woohoo';
typeof val; // returns "string"
val = true;
typeof val; // returns "boolean"
val = null;
typeof val; // returns "object", an ERROR
The laughable error is typeof
on a null
variable returns "object"
when instead it should return "null"
, but it’s too late to correct. Because of this we’re forced to check for null
by
if(val === null) {
...
}
typeof
is of no use for distinguishing objects.
var val = {a:1, b:2}; // creates new Object object
typeof val; // returns "object"
val = [1,2,3]; // creates new Array object
typeof val; // returns "object"
val = new RegExp('a+b'); // creates new RegExp object
typeof val; // returns "object"
To muddy up the waters even more, it gives us a bonus value.
function doStuff(){} // function declaration
typeof doStuff; // returns "function"
But don’t be misled, functions are not their own type. They’re of the single “object” type[2]. But this "function"
value is still useful. We know that invoking an unknown variable can be risky because it’ll throw a TypeError
if it’s not a function. typeof
provides the safeguard to know whether the variable is a function before you invoke it.
function someFunc(param) {
if(typeof param === 'function') {
param();
}
}
typeof summary #
Pros | Cons |
---|---|
Simple way to identify primitive types | typeof on null variable returns "object" . |
Can be used as a safeguard to identify “functions” before invoking them | Misleads that function is a type when it’s not |
Differentiates primitive types from their object equivalent (e.g. 5 is not same as new Number(5)
|
All object types return “object”. It doesn’t distinguish between objects, such as an Array and RegExp |
instanceof operator #
This second technique is the only way to identify custom objects and can be useful for identifying built-in objects. It checks if a given object was created by a given constructor function[3].
The syntax is
object_reference instanceof ConstructorFunction
In action
// MyObj constructor function
function MyObj(){}
// create new MyObj
var myObj = new MyObj();
myObj instanceof MyObj; // returns true
myObj instanceof RegExp; // returns false
For better or worse, instanceof
traverses up an object’s prototype chain, checking if the object in question is an instance of the given constructor function or any of it’s parent objects.
function ParentObj(){} // parent constructor function
function ChildObj(){} // child constructor function
// set child to inherit from parent
ChildObj.prototype = new ParentObj;
ChildObj.prototype.constructor = ChildObj;
// instantiate instance of child
var child = new ChildObj();
// test if instance of child
child instanceof ChildObj // true
// test if instance of parent
child instanceof ParentObj // true
At times this behavior may be useful, but it’s problematic. It leaves us with no way to determine which constructor function instantiated the object within it’s inheritance chain. By this one check we don’t know if child
was created by ChildObj
or ParentObj
.
instanceof summary #
Pros | Cons |
---|---|
Only way to identify custom objects | Must know object’s constructor function to check against |
Also works for non-static built-in objects | Doesn’t work for primitive types |
Traverses prototype chain to check if instance of parent object | (Also a negative) Can’t verify which constructor function instantiated object within inheritance chain |
Object.prototype.toString #
The third type checking technique is useful for identifying built-in objects.
Whenever an object is created it’s assigned several internal properties under the hood. One of these is the [[Class]]
property whose value is the kind of object it is. For built-in objects this value is the name of the object’s constructor function[5].
var array = []; // new array's internal [[Class]] is Array
var date = new Date(); // new date's internal [[Class]] is Date
We can use this unique, read-only property for type checking. The built-in Object’s toString() method will return this internal [[Class]]
property[4]. We can invoke it with our object in question using the call()
method located on every Function prototype.
var arr = [1,2,3];
Object.prototype.toString.call(arr); // returns "[object Array]"
var reg = new RegExp('a+b');
Object.prototype.toString.call(reg); // returns "[object RegExp]"
Object.prototype.toString.call(JSON); // returns "[object JSON]"
The string format [object [[Class]]] is returned.
Interestingly, primitive types can be also identified using this approach.
var val = 10000; // primitive number
Object.prototype.toString.call(val); // returns "[object Number]"
val = 'abc'; // primitive string
Object.prototype.toString.call(val); // returns "[object String]"
val = true; // primitive boolean
Object.prototype.toString.call(val); // returns "[object Boolean]"
val = null; // primitive null
Object.prototype.toString.call(val); // returns "[object Null]"
val = undefined; // primitive undefined
Object.prototype.toString.call(val);//returns "[object Undefined]"
It’s worth noting custom objects always return [object Object]
, which isn’t very useful. Use instanceof when checking those kinds of objects. The [[Class]]
property values of host (browser) objects varies greatly per browser as it isn’t specified in the language spec.
Object.prototype.toString() summary #
Pros | Cons |
---|---|
Only way to identify all built-in objects | Verbose |
Normalizes primitive types and their corresponding wrapper objects | (Also a negative) Does not differentiate whether a primitive or object |
Summary #
In this post we identified three reliable approaches to type checking, all based on what you’re working with. Primitives are best identified using typeof
with the exception of the null
value. It’s checked by val === null
. Object.prototype.toString
is used when wanting to normalize primitive numbers, strings, booleans with their wrapper object equivalents as being the same. Built-in objects are best identified using Object.prototype.toString
because it identifies types for both static and non-static built-in objects. Custom objects, which are objects defined by the developer, can only be identified using instanceof
, though unfortunately there isn’t a way to distinguish whether the object was constructed by a given constructor or it’s parent object’s constructor. Host objects are best identified using Object.prototype.toString
because most are static. Even so, their values vary greatly between browser implementations.
I hope there was something of value for you in this post. I’d greatly enjoy your comments or questions. Please feel free to reach out on LinkedIn.
References:
[1] - EMCAScript 5.1 spec, http://www.ecma-international.org/ecma-262/5.1/#sec-11.4.3
[2] - EMCAScript 5.1 spec, http://www.ecma-international.org/ecma-262/5.1/#sec-4.3.24
[3] - Resig, J., & Bibeault, B. (2013). Secrets of the JavaScript Ninja (p. 127). Shelter Island, NY: Manning.
[4] - EMCAScript 5.1 spec http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
[5] - ECMAScript 5.1 spec http://www.ecma-international.org/ecma-262/5.1/#sec-8.6.2