Checking if an object is array-like

As best I've found in my research on this topic, you have only a couple choices:

  1. You can look only at the .length property and accept any object that seems to have an appropriate .length property that isn't any other things you know you should eliminate (like a function).

  2. You can check for specific array-like objects (HTMLCollection, nodeList) and bias in favor of them.

Here are two options for the first method - one that doesn't accept a zero length and one that does (these incorporate suggestions by gilly3 and things we see in jQuery's similar function):

// see if it looks and smells like an iterable object, but don't accept length === 0
function isArrayLike(item) {
    return (
        Array.isArray(item) || 
        (!!item &&
          typeof item === "object" &&
          item.hasOwnProperty("length") && 
          typeof item.length === "number" && 
          item.length > 0 && 
          (item.length - 1) in item
        )
    );
}

This, of course, reports false for items with .length === 0, If you want to allow .length === 0, then the logic can be made to include that case too.

// see if it looks and smells like an iterable object, and do accept length === 0
function isArrayLike(item) {
    return (
        Array.isArray(item) || 
        (!!item &&
          typeof item === "object" &&
          typeof (item.length) === "number" && 
          (item.length === 0 ||
             (item.length > 0 && 
             (item.length - 1) in item)
          )
        )
    );
}

Some test cases: http://jsfiddle.net/jfriend00/3brjc/

2) After checking to see that it's not an actual array, you can code to check for specific kinds of array-like objects (e.g. nodeList, HTMLCollection).

For example, here's a method I use when I want to make sure I include nodeList and HTMLCollection array-like objects:

// assumes Array.isArray or a polyfill is available
function canAccessAsArray(item) {
    if (Array.isArray(item)) {
        return true;
    }
    // modern browser such as IE9 / firefox / chrome etc.
    var result = Object.prototype.toString.call(item);
    if (result === "[object HTMLCollection]" || result === "[object NodeList]") {
        return true;
    }
    //ie 6/7/8
    if (typeof item !== "object" || !item.hasOwnProperty("length") || item.length < 0) {
        return false;
    }
    // a false positive on an empty pseudo-array is OK because there won't be anything
    // to iterate so we allow anything with .length === 0 to pass the test
    if (item.length === 0) {
        return true;
    } else if (item[0] && item[0].nodeType) {
        return true;
    }
    return false;        
}

You can check if the object is iterable :

function isIterable(o){
 return (o!=null && typeof(o[Symbol.iterator])==='function');
}

Careful though, returns true for strings. If that's a problem, exclude them :

function isIterable(o){
 return (o!=null && typeof(o[Symbol.iterator])==='function' && typeof(o)!=='string');
}

Then either access the elements using the iterator or if you want to use the regular array[0] way, just add a check for length property.


Complete isArrayLike function :

function isArrayLike(a){
 return (
  a!=null &&
  typeof(a[Symbol.iterator])==='function' &&
  typeof(a.length)==='number' &&
  typeof(a)!=='string'
 );
}

Well, it depends what you mean by array-like. Probably something you could iterate with a for loop like this:

for (var i=0, l=obj.length; i<l; ++i) {
  var item = obj[i];
}

So then the test is simple:

function isArrayLike(obj) {
  if (!obj) return false;
  var l = obj.length;
  if (typeof l != 'number' || l < 0) return false;
  if (Math.floor(l) != l) return false;
  // fast check
  if (l>0 && !((l-1) in obj)) return false;
  // more complete check (optional)
  for (var i=0; i<l; ++i) {
    if (!(i in obj)) return false;
  }
  return true;
}

Of course, this won't catch arrays which are sparsely populated, but then again, are they really being used as arrays? NodeLists and the like won't be sparsely populated.

Enjoy!