Detecting JavaScript data types
Published on
Last modified on
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.