JavaScript – Array-Like Objects

As we’ve seen, JavaScript arrays have some special features that
other objects do not
have:

  • The length property is
    automatically updated as new elements are added to the
    list.

  • Setting length to a
    smaller value truncates the array.

  • Arrays inherit useful methods from Array.prototype.

  • Arrays have a class attribute of
    “Array”.

These are the features that make JavaScript arrays distinct from
regular objects. But they are not the essential features that define
an array. It is often perfectly reasonable to treat any object with a
numeric length property and
corresponding non-negative integer properties as a kind of
array.

These “array-like” objects actually do occasionally appear in
practice, and although you cannot directly invoke array methods on
them or expect special behavior from the length property, you can still iterate
through them with the same code you’d use for a true array. It turns
out that many array algorithms work just as well with array-like
objects as they do with real arrays. This is especially true if your
algorithms treat the array as read-only or if they at least leave the
array length unchanged.

The following code takes a regular object, adds properties to
make it an array-like object, and then iterates through the “elements”
of the resulting pseudo-array:

var a = {};  // Start with a regular empty object

// Add properties to make it "array-like"
var i = 0;
while(i < 10) {
    a[i] = i * i;
    i++;
}
a.length = i;

// Now iterate through it as if it were a real array
var total = 0;
for(var j = 0; j < a.length; j++)
    total += a[j];

The Arguments object that’s described in Variable-Length Argument Lists: The Arguments Object is an array-like object. In client-side
JavaScript, a number of DOM methods, such as document.getElementsByTagName(), return
array-like objects. Here’s a function you might use to test for
objects that work like arrays:

// Determine if o is an array-like object.
// Strings and functions have numeric length properties, but are 
// excluded by the typeof test. In client-side JavaScript, DOM text
// nodes have a numeric length property, and may need to be excluded 
// with an additional o.nodeType != 3 test.
function isArrayLike(o) {
    if (o &&                                // o is not null, undefined, etc.
        typeof o === "object" &&            // o is an object
        isFinite(o.length) &&               // o.length is a finite number
        o.length >= 0 &&                    // o.length is non-negative
        o.length===Math.floor(o.length) &&  // o.length is an integer
        o.length < 4294967296)              // o.length < 2^32
        return true;                        // Then o is array-like
    else
        return false;                       // Otherwise it is not
}

We’ll see in Strings As Arrays that ECMAScript 5
strings behave like arrays (and that some browsers made strings
indexable before ECMAScript 5). Nevertheless, tests like the one above
for array-like objects typically return false for strings—they are usually best
handled as strings, not as arrays.

The JavaScript array methods are purposely defined to be
generic, so that they work correctly when applied to array-like
objects in addition to true arrays. In ECMAScript
5, all array methods are generic. In ECMAScript 3, all
methods except toString() and
toLocaleString() are generic. (The
concat() method is an exception:
although it can be invoked on an array-like object, it does not
property expand that object into the returned array.) Since array-like
objects do not inherit from Array.prototype, you cannot invoke
array methods on them directly. You can invoke them indirectly using
the Function.call method,
however:

var a = {"0":"a", "1":"b", "2":"c", length:3};  // An array-like object
Array.prototype.join.call(a, "+")  // => "a+b+c"
Array.prototype.slice.call(a, 0)   // => ["a","b","c"]: true array copy
Array.prototype.map.call(a, function(x) { 
    return x.toUpperCase();
})                                 // => ["A","B","C"]:

We’ve seen this call()
technique before in the isArray()
method of Array Type. The call() method of Function objects is covered
in more detail in The call() and apply() Methods.

The ECMAScript 5 array methods were introduced in Firefox 1.5.
Because they were written generically, Firefox also introduced
versions of these methods as functions defined directly on the
Array constructor. With these
versions of the methods defined, the examples above can be rewritten
like this:

var a = {"0":"a", "1":"b", "2":"c", length:3};  // An array-like object
Array.join(a, "+")
Array.slice(a, 0)
Array.map(a, function(x) { return x.toUpperCase(); })

These static function versions of the array methods are quite
useful when working with array-like objects, but since they are
nonstandard, you can’t count on them to be defined in all browsers.
You can write code like this to ensure that the functions you need
exist before you use them:

Array.join = Array.join || function(a,sep) {
    return Array.prototype.join.call(a,sep);
};
Array.slice = Array.slice || function(a,from,to) {
    return Array.prototype.slice.call(a,from,to);
};
Array.map = Array.map || function(a, f, thisArg) {
    return Array.prototype.map.call(a, f, thisArg);
}

Comments are closed.