`instanceof` considered harmful (or how to write a robust `isArray`)
Checking types in Javascript is well known as a pretty unreliable process.
Good old typeof operator is often useless when it comes to certain types of values:
typeof null; // "object" typeof []; // "object"
People often expect to see something like “null” in the former check and something like “array” in the latter one.
Fortunately, checking for null is not that hard, despite useless typeof, and is usually accomplished by strict-comparing value to null:
value === null;Checking for arrays, on the other hand, is a somewhat tricky business. There are usually two schools of thought - using instanceof operator (or checking object’s constructor property) and the-duck-typing way - checking for presence (or types) of certain set of properties (which are known to be present in array objects).
Obviously, both ways have their pros and cons.
1) `instanceof` operator / `constructor` property
instanceof operator essentially checks whether anything from left-hand object’s prototype chain is the same object as what’s referenced by prototype property of right-hand object. It sounds somewhat complicated but is easily understood from a simple example:
var arr = []; arr instanceof Array; // true
This statement returns `true` because Array.prototype (being a prototype property of a right-hand object) references the same object as an internal [[Prototype]] of left-hand object ([[Prototype]] is “visible” via arr.__proto__ in clients that have __proto__ extension). An alternative constructor check, which I mentioned earlier, would usually look like:
var arr = []; arr.constructor == Array; // true
Both instanceof and constructor look very innocent and seem like great ways to check if an object is an array. If I remember correctly, latest jQuery is using constructor:
An excerpt from jQuery (rev. 5917):
... isArray: function( arr ) { return !!arr && arr.constructor == Array; } ...
The problems arise when it comes to scripting in multi-frame DOM environments. In a nutshell, Array objects created within one iframe do not share [[Prototype]]’s with arrays created within another iframe. Their constructors are different objects and so both instanceof and constructor checks fail:
var iframe = document.createElement('iframe'); document.body.appendChild(iframe); xArray = window.frames[window.frames.length-1].Array; var arr = new xArray(1,2,3); // [1,2,3] // Boom! arr instanceof Array; // false // Boom! arr.constructor === Array; // false
This “problem” was mentioned by Crockford as far as back in 2003. Doug suggested to try duck-typing and check for a type of one of the Array.prototype methods - e.g.:
typeof myArray.sort == 'function'
Exactly for these reasons Javascript authors often resort to a second approach:
2) Duck-typing
We’ve been using it in Prototype.JS for quite some time now. Dean Edwards was using it in its base2, last time I looked at it.
An excerpt from Prototype.js (v. 1.6.0.3):
function isArray(object) { return object != null && typeof object === "object" && 'splice' in object && 'join' in object; }
By “fixing” multi-frame “problem”, this naive approach fails short in some of the trivial cases. If you were ever to have an object with splice and join properties, Object.isArray would obviously detect that object as being an Array:
var testee = { splice: 1, join: 2 }; Object.isArray(testee); // true
Back in June, I was reading ECMA-262 specs and noticed that there was an easy way to get value of an internal [[Class]] property that every native object has. Object.prototype.toString was defined like so:
Object.prototype.toString( )
When the toString method is called, the following steps are taken:
1. Get the [[Class]] property of this object.
2. Compute a string value by concatenating the three strings “[object “, Result (1), and “]”.
3. Return Result (2)
Contrary to Function.prototype.toString which is implementation dependent and is NOT recommended to be relied upon, Object.prototype.toString has a clearly defined behavior for all native objects.
15.3.4.2 Function.prototype.toString()
An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation string is implementation-dependent.
Just as a fun exercise, I wrote a simple __getClass method, put it into an “experimental” folder and forgot about it : )
function __getClass(object) { return Object.prototype.toString.call(object) .match(/^\[object\s(.*)\]$/)[1]; };
A couple of weeks ago, though, someone created a ticket for Prototype.js - proposing an Object.isDate method. An implementation used constructor check and so was vulnerable to cross-frame issues. This is when I remembered about getClass and its possible usage in isArray, isDate and other similar methods.
Specs mention that:
15.4.2.1 new Array([ item0[, item1 [,…]]])
…
The [[Class]] property of the newly constructed object is set to “Array”.
…
This means that creating isArray function could not be simpler than:
function isArray(o) { return Object.prototype.toString.call(o) === '[object Array]'; }
The solution is not dependent on frames (since it checks internal [[Class]]) and is more robust than duck-typing approach. I have tested it on a handful of browsers (including some archaic and mobile ones) and was happy to find that all of them are indeed compliant in this regard.
Let’s hope this little “trick” serves as a remedy to cross-frame issues that authors struggle to find workarounds for : )
Happy new year!
Seb said:
#Happy New Year kangax!
And thanks for the post
Fabian Jakobs said:
#This made my day! Some days ago I had the problem to detect whether a variable, which came from a different frame, contained a function but neither instanceof nor duck typing can do this. The typeof operator was no option either because RegExp are typeof function as well. With your solution I can finally do this test.
Chris said:
#Very nice!
John Resig said:
#Just to clarify: As of 1.3 jQuery no longer uses instanceof or .constructor - for the same reasons that you list here. We were especially hitting problems when dealing with cross-frame pages.
Nicolas said:
#Very interesting post!, Thanks!
Dean Edwards said:
#Yes, I was using duck-typing in base2 (for Arrays only). I will switch to this method in the next release though.
Fabian Jakobs said:
#First experiments with this approach look very promising. I think we'll use it in the next qooxdoo release as well.
kangax (article author) said:
#@John
Good to know jQuery is cross-frame safe now. What exactly are you guys using?
@Fabian
Glad it helped : ) It's also worth mentioning that same approach can be used with `Date`, `RegExp` and other common objects.
Simon said:
#Nice finding!
I was always feeling a bit uncomfortable about the duck typing approach.
Btw, your avatar links to http:///
kangax (article author) said:
#@Simon
Thanks, fixed now.
John Resig said:
#@kangax: We use the Object.prototype.toString technique.
http://dev.jquery.com/browser/trunk/jquery/src/core.js#L607
James said:
#Nice write-up!
If I know I'm not going to be using any frames then I usually just use this:
Array.prototype.isArray = true;alert( ([]).isArray ); // trueKeeto said:
#Mootools has an interesting approach with it's $type function. Mootools wraps all native objects via the "Native" constructor in order to easily implement new methods for them. The Native constructor also creates a new property, called $family, which is the value returned by the $type() utility function.
kangax (article author) said:
#@James
That would, unfortuantely, lead to false results in cases when truthy `isArray` property is present in an object (or in object's prototype chain - exactly what happens in your example - where property is found in an object referenced by `Array.prototype`). While it could work in "closed" systems, it doesn't seem like a viable solution in the wild : )
@Keeto
Interesting, although sounds a bit too obtrusive : )
Timothy said:
#This is really useful. Thanks a lot!
aNieto2k said:
#Hi, I found an interesting data:
var t = new Date().getTime();
for (var x =0; x
aNieto2k said:
#Sorry, bad code (del previous comment).
var t = new Date().getTime();
for (var x =0; x<10000; x++){
var a = [];
a instanceof Array;
}
console.log(new Date().getTime() - t);
// 32ms.
var t = new Date().getTime();
for (var x =0; x<10000; x++){
var b = [];
b.constructor == Array
}
console.log(new Date().getTime() - t);
// 82ms.
Ted Henry said:
#See also Crockford's Blog where he credits Mark Miller.
Luke said:
#@kangax
Thanks so much for sharing this. Really a weight off so many web devs' shoulders.
On the topic of using the method with other native types, I put together this quasi typeof function that uses this method.
http://gist.github.com/47997
Gloridea said:
#There's a problem to apply this way to check the array.
In multi-frame DOM environment which is mentioned above,
IE doesn't detect the array from other frame, however, all other browsers do correctly.
In IE, Object.prototype.toString.call(arrayFromOtherFrame) returns [object Object].
I think I have to apply both of the discernment method; Object.prototype.toString and duck typing.
kangax (article author) said:
#@Gloridea
I couldn't reproduce IE returning "[object Object]". Could you, perhaps, attach a simple test case which replicates it, and I'll try to look into it.
Jordan said:
#Would it not make more sense to have this:
Object.prototype.toString.call(o) === Object.prototype.toString.call([]);
Then you aren't depending on the text, just that the array literal and the object are the same.
kangax (article author) said:
#@Jordan
I don't see anything wrong with string comparison.Your example creates an array object every time method is called (so is probably less memory efficient).It also calls
Object.prototype.toStringtwice (and so is probably slower). Why do you want to avoid "text dependence"?Jordan said:
#You're right, and I don't have a good reason not to use "[object Array]", I was just thinking out loud.
Diego Perini said:
#Juriy,
very nice addition to the detection tools serie. These few bits will be essential to improve the quality of our code.
Sometime I need to detect if a method is natively implemented by the browser running our code. The above trick will only tell if it is a "[object Function]", but it maybe a function extended by another piece of code or framework over which we have no control.
So while writing my project I found very handy to have an isNative() check like this:
// detect native method in object // not same scope of isHostObject isNative = function(object, method) { return object && method in object && typeof object[method] != 'string' && // IE & W3C browser return "[native code]" // Safari < = 2.0.4 will return "[function]" (/{s*[native code]s*}|^[function]$/).test(object[method]); }this is how I detect the method is natively implemented in the browsers and not just extended/added by third party code.
Your advice on the above is really appreciated and tell if you see it can be fooled in some way or maybe you can suggest a better way to do it.
Great work.
Diego
RobG said:
#It seems reasonable to include a link to a discussion that includes this technique on comp.lang.javascript:
RobG said:
#Sorry, the link:
http://groups.google.com.au/group/comp.lang.javascript/browse_frm/thread/368a55fec19af7b2/efea4aa2d12a3aa4?hl=en&lnk=gst&q=+An+isArray+test+(and+IE+bugs)+#efea4aa2d12a3aa4.
kangax (article author) said:
#Thanks for the link, Rob. That was a good discussion.
Douglas Crockford said:
#The next edition of ECMAScript will probably have an Array.isArray(value) function that will return true if the value is actually an array.
kangax (article author) said:
#@Douglas Crockford
That's good to know. And, how exactly will a value be tested? [[Class]], presence of `Array.prototype` in prototype chain or maybe special [[Put]]?
On a side note, `Object.isArray` would make more sense to me than a somewhat repetitively looking `Array.isArray`, although, given addition of bunch of "static" Object extensions in ES3.1, I can see how a committee would want to offload this from `Object` to `Array`.
Diego Perini said:
#Hope Douglas statement implicitly means that Array having different constructors (from other contexts) will still return true ;-)
Paul ROYE said:
#Hi !
Thanks for this page.
But the Object.prototype.toString.call(object) does not work with my current project.
I use IE 7, and I have to compare two objects created in two different windows (multi frame/iframe).
The two objects use the same class to be created.
But Object.prototype.toString.call(object) gives me [object Object]..
The checking method way works well.
typeof(n.method) == "function" gives me true
kangax (article author) said:
#@Paul ROYE
I'd love to take a look at a test case. Could you provide one?
RobG said:
#@Jordan
If you want to use a string comparison created by calling Object.prototype.toString, there is no need for double calls or calling every time, just store the value in a closure:
var isArray = (function() {
var testString = Object.prototype.toString.call([]);
return function(o) {
return Object.prototype.toString.call(o) == testString;
};
})();
Seems to me that will reduce the issues of buggy implementations returning funny strings, they might be few and far between anyway.
There should be no need to use it with functions as jQuery does, with typeof should be sufficient there (naturally host objects are in a league of their own for any of this stuff).
Richard Cornford explains why it doesn't work for IE across frames:
Rob.
James said:
#It just occurred to me that you could use a native JavaScript method that expects an array (e.g. 'apply()') to test for a real array, for example:
function isArray(arr) {
var out = false;
try {
(function(){}).apply({},arr);
out = true;
} catch(e) {}
return out;
}
Diego Perini said:
#@James,
great trick thank you, it was just there under our eyes...try/catch let us do many of these trick.
Unfortunately I cannot make it to work in Opera (9.63). It seems Opera accepts also an Object as a second parameter for ".apply()", so no "Type Exception" there. A fall back is needed in this case.
Also maybe just return "true" in the try block and "false" in the catch block to avoid the only variable you have.
kangax (article author) said:
#James,
I'm afraid using `Function.prototype.apply` with try/catch is probably not strict enough for a general-purpose `isArray`. If you look at specs for `apply`, you can see that both - an array object and an `arguments` object - are valid values (and don't make `apply` throw TypeError). The problem is that an `arguments` object, as I'm sure you know, does not inherit from `Array.prototype` and so doesn't have any of `Array` methods - push, pop, slice, etc. If I check something with `isArray` and get `true` I would expect that something to have all of those methods.
var args = (function(){ return arguments; })();
if (isArray(args)) {
args.slice(1); // boom!
}
Perhaps, a better name for the method in your example would be `isArrayLike`.
Another problem with try/catch is how slow it is comparing to other alternatives. I mentioned a try/catch -based alternative in ajaxian comments some time ago, but I would not recommend using it in production code for performance reasons.
function isArray(o) {
try {
Array.prototype.toString.call(o);
return true;
} catch(e) {
return false;
}
}
Moreover, there's another problem with try/catch -based solutions and it is that there's no guarantee than an error is related to an object not being an array. An error could be related to something else which would lead to false positives. Javascript's error facilities are, unfortunately, quite poor and unreliable.
For example, in my previous snippet, we rely on `Array.prototype.toString` throwing error when it is called in a context of a non-array object. The problem is that `Array.prototype.toString` calls `toString` of each of array elements and if one of those methods happens to throw an error (which is, of course, idiotic, but is still a possibility), we would erroneously end up in a wrong branch : )
function isArray(o) {
try {
Array.prototype.toString.call(o);
return true;
} catch(e) {
return false;
}
}
var o = [ { toString: function(){ throw new TypeError(); } }, 2, 3];
isArray(o); // false
I hope this clarifies things a bit.
Serkan Yerşen said:
#This is the easiest & simplest way I can think of:
function isArray(obj){
return obj? !!obj.push : false;
}
Isn't this function valid?
kangax (article author) said:
#@Serkan Yerşen
I actually explain duck typing (which your example is) in the post. Is it valid? That depends on how you define valid, of course.
Serkan Yerşen said:
#Oh Sorry, You are right, I didn't notice that in the post. I actually read the summary of your post from the Ajaxian :) Anyways my example can be secured for this type of problems but it will no be efficient, simple or short anymore.
Thank you for your answer.