Have you ever wondered: what is the correct way to check if a Javascript variable is an Array?
Do a Google search and you will get a great variety of answers. And, unfortunately, there's is no correct answer. This is one of the sad things about Javascript, not only are there many varying implementations of the language, there are also many varying opinions about how things should be done.
Enough philosophizing and feeling sorry about the state of things. With this article, I will trying to give a comprehensive overview of the different techniques of checking-types in Javascript, the pros and cons of each and why they exist.
typeof operator
In the beginning, there was typeof. This handy operator gives you the "type" of a Javascript value:
typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
All is fine 'n dandy until
typeof [] // "object"
Huh? An array's type is object? I guess it is, if you want to get technical about it, but still, what the...
typeof null // "object"
Okay, now that's just wrong!
Also, typeof will return "object" for Dates, RegExp, user defined objects, DOM elements, and pretty much anything else. So, typeof is pretty good at distinguishing between different kind of primitive values, and distinguish between them and objects, but is completely useless when it comes to distinguishing between different kinds of objects - which includes arrays and nulls(WTF?!).
instanceof operator
The instanceof operator tells you whether a object is an instance of a certain type. The so-called "type" is a constructor. For example
function Animal(){}
var a = new Animal()
a instanceof Animal // true
Alternatively, you could make this check using the constructor property of an object
a.constructor === Animal // true
However, the constructor check has two problems. First, it does not walk up the prototype chain
function Cat(){}
Cat.prototype = new Animal
Cat.prototype.constructor = Cat
var felix = new Cat
felix instanceof Cat // true
felix instanceof Animal // true
felix.constructor === Cat // true
felix.constructor === Animal // false
The second problem is that it will bomb out if the object in question is null or undefined.
felix = null
felix instanceof Animal // true
felix.constructor === Animal // throws TypeError
[1, 2, 3] instanceof Array // true
/abc/ instanceof RegExp // true
({}) instanceof Object // true
(function(){}) instanceof Function // true
or does it?
3 instanceof Number // false
true instanceof Boolean // false
'abc' instanceof String // false
Okay, what is going on here? It turns out that instanceof does not work with primitive values. The primitive types in Javascript are: strings, numbers, booleans, null, and undefined - (Oh good! You can count them all on one hand!) Well actually, I should have said it does not work with primitives with the exception of null and undefined, because they are not an instance of anything, and so instanceof correctly returns false when either is used on the left hand side.
null instanceof Boolean // false
undefined instanceof Array // false
To top that off though, checking for the constructor property will work for the primitive types number, string and boolean.
(3).constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true
This works because whenever you reference a property on a primitive value, Javascript will automatically wrap the value with an object wrapper, like so
var wrapper = new Number(3)
except you don't see this - it happens behind the scenes. The wrapper then will be an instance of - in this case Number - or a Boolean or a String depending on the type of the primitive value, at which point it can walk up the prototype-chain and get at the properties of the Number prototype, etc. So, for example, creating a wrapper manually will make the instanceof operator work
new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String // true
But doing that would be pointless because it requires you to already know the type of the value of which you are asking whether or not it is of the type that you already know it is.
Cross-window Issues of instanceof
It turns out that instanceof has another problem. It breaks down when you try to test an object coming from another window. You know? The ones that are created for each <iframe>, <frame> or popup window that you create.
var iframe = document.createElement('iframe')
document.body.appendChild(iframe)
var iWindow = iframe.contentWindow // get a reference to the window object of the iframe
iWindow.document.write('<script>var arr = [1, 2, 3]</script>') // create an array var in iframe's window
iWindow.arr // [1, 2, 3]
iWindow.arr instanceof Array // false
Above, we created a variable arr inside the context of the iframe and set it to the array [1, 2, 3]. However, we get false when we ask whether it is a instance of Array. What is happening?!! Behold.
Array === iWindow.Array // false
The Array in the iframe is not the same Array as our Array! This is true for all built-in objects: there are two versions of all of them! Basically, we have parallel universes! OMG!
What this means is that an array created within the iframe is only an instance of the Array constructor within the iframe
iWindow.arr instanceof iWindow.Array // true
The same phenomenon happens with windows created using the open() function. It is because of this cross-window problem that the use of instanceof is somewhat limited and discouraged within the Javascript community.
Duck-Typing
Because neither typeof or instanceof are satisfactory, many resort to duck-typing. This means checking the behavior: if it looks like a duck and quacks like a duck, then it is a duck as far as I am concerned. Pretty sure I misquoted that...oh well.
So, using duck-typing, an isArray check might look like
// source: http://forums.devshed.com/javascript-development-115/javascript-test-whether-a-variable-is-array-or-not-33051.html
function isArray(obj){
return (typeof(obj.length)=="undefined") ?
false:true;
}
or
// source: http://www.hunlock.com/blogs/Ten_Javascript_Tools_Everyone_Should_Have
function isArray(testObject) {
return testObject &&
!(testObject.propertyIsEnumerable('length')) &&
typeof testObject === 'object' &&
typeof testObject.length === 'number';
}
in jQuery, the function to check whether an object is a window is
isWindow: function( obj ) {
return obj && typeof obj === "object" && "setInterval" in obj;
}
You could easily trick this function into returning true
$.isWindow({setInterval: 'bah!'}) // true
Clearly, the problem with this approach is that
- it is inexact and can have false positives
- the set of properties of the object to test is arbitrary, and so it's unlikely for different people to agree on one way of doing it
Object.prototype.toString method
It turns out that, you can get type information about an object by using the Object.prototype.toString method.
Object.prototype.toString.call(3) // "[object Number]"
Object.prototype.toString.call([1, 2, 3]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
This native method is rarely encountered normally because it's usually shadowed by another toString method lower down in the prototype chain(Array.prototype.toString, Number.prototype.toString, etc.). This method reliably differentiates between native types, however, will return "[object Object]" for all user-defined types
Object.prototype.toString.call(new Animal) // "[object Object]"
It does, however, work across different window contexts
Object.prototype.toString.call(iWindow.arr) === "[object Array]" // true
with one exception: different windows(as in popup window) in IE.
var pWindow = open("")
pWindow.document.write('<script>var arr = [1, 2, 3]</script>')
Object.prototype.toString.call(pWindow.arr) // you get "[object Object]" in IE; "[object Array]" else where.
This is strange because for iframes it works just fine, bummer! This method has become somewhat of a preferred way to differentiate native types despite the IE bug. Ehh, nobody uses popup windows anymore anyway.
Function.prototype.toString method
Yet another way to test for type information is by using the Function.prototype.toString method.
Function.prototype.toString.call((3).constructor)
// "function Number() {
// [native code]
// }"
The method gives you the complete source of a function. In the case of native functions, it just says "[native code]" in the body. One could easily parse out the name of the function to figure out type of the object using this helper function
function type(obj){
var text = Function.prototype.toString.call(obj.constructor)
return text.match(/function (.*)\(/)[1]
}
type("abc") // "String"
This one will work for user-defined types too
type(new Animal) // "Animal"
this code has a problem wrt popup windows in IE just like instanceof. It's because when Function.prototype.toString is called with a constructor from another parallel universe, it can only discern the constructor as an object("[object Object]"), and thus rejects the argument and throws a "Function expected" error. This problem can actually be worked around by referencing the toString method indirectly
function type(obj){
var text = obj.constructor.toString()
return text.match(/function (.*)\(/)[1]
}
Now, it works for popup windows in IE too! This fix makes it susceptible to shadowing
Array.toString = function(){ return "function NotArray(){ }" }
type([1,2,3]) // "NotArray"
but still, I'd say this is pretty cool.
Now, let's return to user-defined types for a minute. With this approach, there's no way to distinguish between two different user-defined types with the same name
var f = function Animal(){ "something" }
var g = function Animal(){ "something entirely different" }
type(new f) // "Animal"
type(new g) // "Animal"
For this reason, this method is inferior to instanceof for user-defined types. Another seemingly obvious problem with this approach is performance, but I'd have to benchmark it(jsperf!) to make a real claim.
DOM Elements and Host Objects
So far, I have not mentioned type checking for DOM elements and host objects. That's because it's hard. With the exception of duck-typing, none of the methods mentioned above will work for all browsers. If you drop IE7 and below, however, you can actually get some of the things to work. The output below were created using Tutti
> var div = document.createElement('div')
> typeof div
Safari 5.0 => object
Firefox 3.6 => object
IE 7.0 => object
IE 8.0 => object
Opera 11.01 => object
> div instanceof Element
Safari 5.0 => true
Firefox 3.6 => true
IE 7.0 => Error: 'Element' is undefined
IE 8.0 => true
Opera 11.01 => true
> div instanceof HTMLDivElement
Safari 5.0 => true
Firefox 3.6 => true
IE 8.0 => true
IE 7.0 => Error: 'HTMLDivElement' is undefined
Opera 11.01 => true
First up, typeof is useless, that was expected. Next, everyone but IE 7 recognizes that a div is an instance of Element as well as an HTMLDivElement. In IE7, those constructors aren't even present. Next,
> Object.prototype.toString.call(div)
Safari 5.0 => [object HTMLDivElement]
Firefox 3.6 => [object HTMLDivElement]
IE 7.0 => [object Object]
IE 8.0 => [object Object]
Opera 11.01 => [object HTMLDivElement]
The result of Object.prototype.toString in IE - even IE 8 - is particularly unhelpful. What about
> div.constructor.toString()
Safari 5.0 => [object HTMLDivElementConstructor]
Firefox 3.6 => [object HTMLDivElement]
IE 7.0 => Error: 'div.constructor' is null or not an object
IE 8.0 => [object HTMLDivElement]
Opera 11.01 => function HTMLDivElement() { [native code] }
Function.prototype.toString: it gives us something useful for IE8, but every browser has a slightly different output.
Fun! Try another one? Allllllllllllllllrrrighty then!
> typeof window
Safari 5.0 => object
Firefox 3.6 => object
IE 8.0 => object
IE 7.0 => object
Opera 11.01 => object
> window instanceof Window
Safari 5.0 => ReferenceError: Can't find variable: Window
Firefox 3.6 => true
IE 8.0 => true
IE 7.0 => Error: 'Window' is undefined
Opera 11.01 => ReferenceError: Undefined variable: Window
> Object.prototype.toString.call(window)
Safari 5.0 => [object DOMWindow]
Firefox 3.6 => [object Object]
IE 8.0 => [object Object]
IE 7.0 => [object Object]
Opera 11.01 => [object Window]
> window.constructor
Safari 5.0 => function Object() {
[native code]
}
Firefox 3.6 => function Object() {
[native code]
}
IE 8.0 => [object Window]
IE 7.0 => undefined
Opera 11.01 => function Object() { [native code] }
With window it is just all over the place, none of these methods worked for all browsers. You can try testing out some other host objects if you want, but in general it doesn't look doable. However, in my testing, the XMLHttpRequest object and DOM and Text elements look doable using instanceof, if you can drop support for IE7 and below.
What We've Learned
Checking types in Javascript is a big mess. Although it's really not that hard to check for any one particular type, it is definitely not consistent across types, and you probably have had to make a lot of choices along the way. So it helps to know about all the different options. In an upcoming post, I will try to make all of this easier with a small piece of code. Stay tuned.