7

Identifying which objects are which is complicated in JavaScript, and figuring out which objects are arrays has something of a hacky solution. Fortunately, it manages to work in both of the following cases:

Object.prototype.toString.call([]);           // [object Array]
Object.prototype.toString.call(new Array());  // [object Array]

Great, no [object Object] in sight! Sadly, this method still manages to fail with this:

var arr = Object.create(Array.prototype);
Object.prototype.toString.call(arr);          // [object Object]

This is frustrating, so say the least. My arr object has all the methods of an array, it functions like an array, and for all purposes, it is an array. Yet JavaScript doesn't provide the tools to identify it as such.

Is there any way to figure out if an object inherits from a particular prototype? I suppose you could iterate through the prototypes like so:

function inherits(obj, proto) {
    while (obj != null) {
        if (obj == proto) return true;
        obj = Object.getPrototypeOf(obj);
    }
    return false;
}

inherits(Object.create(Array.prototype), Array.prototype);  // true

But it feels a tad hacky. Is there any cleaner approach?

Community
  • 1
  • 1
Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • I'm just curious: why would you ever create an array like this? `Object.create(Array.prototyp);` and check it via `Object.prototype.toString.call(arr);` – Zim84 May 24 '13 at 08:06
  • @Zim84: I think this is just an example. In real applications, we should never do this. In real applications, the Object.create(Array.prototype) may be called somewhere else and pass the created object to another function – Khanh TO May 24 '13 at 08:09
  • The problem is you don't really have a true array (for instance it doesn't have the magical `length` property, just one that looks like it at first sight). What you have is an array-like which just happens to have all the methods of a true array. If you want to check for array-likes, you should basically just look to see if it has a `length` property and isn't a string. That's the best IMO. If you want to check for true arrays, keep using `Object.prototype.toString.call` or `Array.isArray` and know that an object with prototype of `Array.prototype` isn't necessarily a true array. – Nathan Wall May 26 '13 at 01:25

5 Answers5

2

How about an instanceof operator? It returns true for all your cases:

[] instanceof Array //true
new Array() instanceof Array //true
Object.create(Array.prototype) instanceof Array //true

However:

Object.create(Array.prototype) instanceof Object //also true

So be careful.

Artyom Neustroev
  • 8,627
  • 5
  • 33
  • 57
  • instanceof Array doesn't provide a reliable way to check when we pass arrays back and forth between frames because each frame has its own Array constructor – Khanh TO May 24 '13 at 08:00
  • This is a reasonable solution for single-frame environments or Node. Just keep in mind the multi-frame issue. – Nathan Wall May 26 '13 at 01:27
2

ECMAScript 5 has introduced Array.isArray() into javascript which provides a reliable way to check. For older browsers, we fix that by (quoted from this book)

function isArray(value) {
    if (typeof Array.isArray === "function") {
         return Array.isArray(value);
    } else {
         return Object.prototype.toString.call(value) === "[object Array]";
    }
}

But i just found out the built-in function Array.isArray does not work correctly when we use Object.create (tested in chrome). I came up with a method that works:

function isArray(value) {
     if (typeof value === "undefined" || value === null) {
          return false;
     }
     do {
          if (Object.prototype.toString.call(value) === "[object Array]") {
               return true;
          }
          value= Object.getPrototypeOf(value);
     } while (value);

     return false;
}

Use it:

var arr = Object.create(Array.prototype);
var arr1 = Object.create(Object.create(Array.prototype));
var arr2 = new Array();
var arr3 = [];
isArray(arr); 
isArray(arr1); 
isArray(arr2); 
isArray(arr3); 
Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • 1
    `Array.isArray` is the exact same as `Object.prototype.toString.call(arr) == '[object Array]'`. This solution won't work if the object's prototype is an object which has a prototype which is an array. `var arr = Object.create(Object.create(Array.prototype));` – Nathan Wall May 26 '13 at 01:24
  • @NathanWall: take a look at this [book](http://books.google.com.vn/books?id=J5RCV8yM7sEC&pg=PA88&lpg=PA88&dq=maintainable+javascript+detecting+array&source=bl&ots=TDZEDzhWJm&sig=I92iJt7mAz8rEdHteDFAmCSqLzI&hl=vi&sa=X&ei=XHShUZnkLY6diAerxICgCg&ved=0CDEQ6AEwAQ#v=onepage&q=maintainable%20javascript%20detecting%20array&f=false) – Khanh TO May 26 '13 at 02:41
  • 1
    Looks better. Just one nitpick: It doesn't test if something is a true array (as described in my comment to the original question). This really tests if something was constructed as an array or inherits from something which was constructed as an Array (which is different). So maybe "isArray" is not the best name for the function. However, this solution satisfies the original question and is probably the best the asker can get to satisfy his conditions. He just needs to be aware of what's really going on. – Nathan Wall May 26 '13 at 03:41
  • @NathanWall: updated to test also the current object. Thanks for pointing out. – Khanh TO May 26 '13 at 03:46
  • What you've done is essentially implement `isPrototypeOf` yourself. `isPrototypeOf` walks the chain for you, as you are doing manually. –  Jul 04 '13 at 16:10
  • @torazaburo I guess the question then is whether `isPrototypeOf` has the same problems with multiple windows as does `instanceof` ([source](http://web.mit.edu/jwalden/www/isArray.html)). (I don't know) – Matt Fenwick Mar 05 '14 at 17:25
2

See http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ for the definitive exposition of the problems in inheriting from Array.

Anyway, in the simplest case, where you are doing

var sort_of_an_array = Object.create(Array.prototype);

you can check using isPrototypeOf:

Array.prototype.isPrototypeOf(sort_of_an_array)

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf.

0

How about checking the constructor?

function inherits(obj, proto) {
    return obj.constructor === proto.constructor;
}

inherits(Object.create(Array.prototype), Array.prototype);  // true
zach
  • 1
0

it functions like an array, and for all purposes, it is an array

No. It has no auto-updating length property. See this article why it's quite impossible to subclass Array.

Is there any way to figure out if an object inherits from a particular prototype? I suppose you could iterate through the prototypes, but it feels a tad hacky.

That's just how to do it. A cleaner approach than a self-written function would be to use the instanceof operator:

arr instanceof Array; // true
Bergi
  • 630,263
  • 148
  • 957
  • 1,375