I'm reviewing my understanding of JavaScript with Professional JavaScript for Web Developers, 3rd edition and one of the bits that came up was detecting the type of a variable in JavaScript.

I decided to review the different methods used to detect data types and understand the strengths and weaknesses of each.

Ways to detect JavaScript types

typeof

The quickest way to identify a variable is with the typeof operator:

var str = "dfranklinweb";
var num = 50;
var bool = true;
var obj = {};
var empty = null;
var func = function() {};

alert(typeof str); // "string"
alert(typeof num); // "number"
alert(typeof bool); // "boolean"
alert(typeof obj); // "object"
alert(typeof empty); // "object"
alert(typeof func); // "function"
alert(typeof nonexistant); // "undefined"; since `nonexistant` was never defined.

It's straight forward for primitive data types, but the empty variable returns object.

The exact reason for this is a bit messy but it is due to how early implementations of JavaScript worked. To avoid creating inconsistencies the functionality was maintained and is now officially documented in the ECMAScript specification.

Even though it's a little weird, it is an edge case. There is, however, another, more common case that makes typeof's reporting less than reliable:

var obj = {};
var arr = [1, 2, 3];

alert(typeof obj); // "object"
alert(typeof arr); // "object"

Both the array and object are returning object—this is the main limitation of typeof, and the following methods aim to solve this.

ECMAScript 5 offers the isArray method, which is available on the global Array constructor and is the best way to test if something is an array.

alert( Array.isArray([1, 2, 3]) ); // true

instanceof

The instanceof operator checks if an object matches a constructor. While it doesn't say what an object is, it can still be used to verify objects, particularly those passed as arguments to functions.

var obj = {};
var arr = [1, 2, 3];
var date = new Date('2016-01-31');

alert(obj instanceof Object); // true
alert(arr instanceof Array); // true
alert(date instanceof Date); // true

instanceof only works on variables created using a constructor. For example, compare the two strings and their return values below:

var str = "dfranklinweb";
alert(str instanceof String); // false

var str2 = new String("dfranklinweb");
alert(str2 instanceof String); // true

Unlike typeof, instanceof compares a variable's constructor with a generic constructor and returns true if there is a match.

It should be noted (for the sake of completeness) that instanceof only runs in a global execution context. This will become an issue if code is moving between frames on a single page.

Assuming an <iframe> lives in a HTML document, this can be tested with the following:

alert( [] instance of window.Array ); // true
alert( [] instance of window.frames[0].Array ); // false

Referencing the prototype

The toString method on the prototype property is available on all variables that descend from the Object constructor. This method can be used to work out the constructor of a variable.

The call function can be used to pass a variable to the toString function.

alert( Object.prototype.toString.call([1, 2, 3]) ); // [object Array]
alert( Object.prototype.toString.call( new Date('2016-01-31') ) ); // [object Date]
alert( Object.prototype.toString.call(/regexp/) ); // [object RegExp]

This does not work with custom defined constructors though.

function Person(name) { this.name = name; }
alert( Object.prototype.toString.call( new Person('Daniel') ) ); // [object Object]

But if the test criteria is known beforehand, an object can be verified by using instanceof, as shown earlier.

function Person(name) { this.name = name; }
alert( new Person('Daniel') instanceof Person ); // true

Referencing the constructor

This option is purely for objects with custom constructors, such as in the above Person example.

function Person(name) { this.name = name; }
alert( new Person('Daniel').constructor.name ); // "Person"

The downfall of this is that it cannot be trusted. Depending on how the Person constructor function is defined, the returned value may still be Object. Another pitfall is browser support—this solution won't work in Internet Explorer 8 and lower.

Which method to use?

All signs would point to using Object.prototype.toString.call() but a lot of people have already written about this subject.

Angus Croll has gone over a similar process, creating a helper function for those who truly need to work out what an object is.

T.J. Crowder also reviewed several options, suggesting we stop being so pedantic and go with "duck-typing" instead—that is to say, "if it looks like a duck, it's probably a duck". In the case of JavaScript, if it acts like an array, it probably is an array.

This has the downfall of some objects being similar though, such as arguments having a length property but not technically being an array:

function whatConstructor() { alert( Object.prototype.toString.call(arguments) ); }
whatConstructor(); // [object Arguments] - NOT an Array!
function hasLength() { alert( arguments.hasOwnProperty('length') ); }
hasLength(); // true - an Array would return the same.

Finally, Jason Bunting wrote up a great answer on StackOverflow covering this topic which is worth a read if you want to see a lot more code examples.