Array-like objects

22 July 2013 · 4 minute read · javascript

You’ve possibly head the saying

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. - credit

This is a common adage when talking about Duck Typing in programming, especially when it comes to working with dynamic languages like JavaScript, based on assumptions made about an object you can attempt to infer other details. Statically typed languages on the other hand make it a bit harder to do Duck Typing, that’s not to say it’s impossible.

Due to the dynamic nature of JavaScript we actually come across this quite often with arrays in JavaScript. So what makes an object an array? Well there’s two basic building blocks of an array:

  • Numerical identifiers
  • A length property

So take this code snippet:

var foo = ??;
for (var i = 0; i < foo.length; i++) {
    console.log(foo[i]);
}

From this we can infer that foo is quite possibly an array, it meets our basic requirements to be an array, but like the dangers of duck typing this doesn’t mean that it’s actually an array does it?

Array-like objects

It’s quite common in JavaScript to come across array-like objects, objects that on the surface look like arrays but as soon as you look beneath the surface it’ll become apparent that they aren’t actually arrays. You’ve probably come across these objects in the past and not really given it a second thought, two really common objects are the arguments object and a NodeList (you know, from querySelectorAll). Both of these objects have numerical indexers, length, but no push, pop and so on, basically they don’t inherit Array.prototype.

With both of these objects the fact that they don’t inherit from Array.prototype is a bit of a pain, it means you couldn’t do something like this for example:

var inputs = form.querySelectorAll('input');
var values = inputs.map(function (input) {
        return {
            value: input.value,
            name: input.getAttribute('name')
        };
    });

So this is a pretty simple bit of code, you want to get all the input name/value pairs, maybe to submit them via AJAX but that’s not important, what’s important is we’re using the Array.map method, something very common if you’re doing anything in a modern JavaScript engine (modern being >= IE9).

Making arrays of array-like objects

If you’ve found yourself an array-like object chances are you want to use it like an array, that begs the obvious question, how do we make it an array?

Well there’s a pretty easy solution to this, we have numerical indexes and a length property, so what about a for loop:

var items = [];
for (var i = 0, il = inputs.length; i < il; i++) {
    items.push(inputs[i]);
}

But… for loop’s are so old school there’s got to be a better way. Well there is and here we can look at exploiting JavaScript’s functions. We’ve seen that you can use call and apply to futz with function scope and this we can do to improve our array-like object manipulation.

Futzing slice

When you’re wanting to create new arrays from existing ones the easiest way is using the slice method. The slice method can be neat if you want to take parts of an array between two indexes, but it can also be used if you want to create a whole clone of the array, like so:

var array1 = [1, 2, 3];
var array2 = array1.slice(0);
console.log(array1 !== array2);

By passing 0 we take a slice starting at index 0 and since we provided no end point it’ll go to the length of the array.

But slice is a function just like everything else, you can use call against it.

And where it gets really interesting is when we play with our array-like objects, we can pass that as our context to our slice method:

var items = Array.prototype.slice.call(inputs, 0);

Yep that’s right, slice doesn’t require an array, just something that looks like an array, as far as slice is concerned it looks like a duck, it quacked, so hey, we’ll treat it like a duck, check out the SpiderMonkey source, it really only cares if there’s a length property, pretty neat!

Conclusion

We’ve seen some building blocks over the last few weeks, things we can use to manipulate functions and objects in the interesting ways and this is just another common usage of the patterns.

A small piece of advice, if you’re doing a lot of these calls you can assign slice into a variable which you can use, which will make the minification work a whole lot better:

var slice = Array.prototype.slice;
var items = slice.call(inputs, 0);

Published: 2017-07-30 20:47:37 +1000 +1000, Version: bfd8bbd