Notes on programming by Daniel Mendel

Subclassing Javascript Arrays

Feb 20th, 2013

When I started working on the animation framework glitz.js, I wanted an easy syntax for expressing and traversing a nested tree of Renderable objects. I thought, ”Why not implement it as an array?” Since the scene graph would need to be iterated over very quickly during the render loop, it would be great to just represent the whole thing as a multi-dimentional array – essentially a subclass of array that had some additional properties and methods like render and animate.

1. Ah, Naïveté

Basically, you can invoke methods from the native Array.prototype on any object with a length and get array-like results. This works because javascript objects allow numerical keys, so essentially what we end up with is:

1234

vararrLike={0:'foo',length:1};

At first glance (eg, not in IE<8), this seems to work great. However, there are hidden dragons – let’s compare some further manipulations to a native array.

Uh-oh, there are obviously some drawbacks here. First, setting the length property of a proper Array to 0 clears the array. In fact, this is usually the fastest method of clearing arrays. Second, when you assign a value to an array index that is > length, then the native array will expand to contain it. In short, native arrays have a magical length that we miss out on entirely with the naive approach. As usual, more information can be found in the relevant section of the EMCAscript spec.

2. Direct extension

Okay, so my naive approach is dead in the water – after grieving, I decided to try direct extension. This is exactly what it sounds like: creating Array instances and copying a bunch of methods & properties on to them directly.

The outcome here is exactly what we want – a native array with some extra methods and properties. There’s just one problem, compared to creating raw instances of a native array, it is really, really slow – for example, in Firefox 17.0, it is 26 x slower when adding just two properties.

Now obviously there are a lot of use cases where this performance hit isn’t particularly painful – even at 1/26th speed you can still create many thousands of arrays per second, and probably most uses for glitz.js wouldn’t suffer too badly here. But, this is in an animation framework and if we can speed any part of it up by that much, it increases the domain it can operate in by a fair margin. This brings us to…

3. Having your cake and eating it inside of an iframe

Wouldn’t it be the best if we could just subclass Array and add some methods to the prototype? Of course this would contravine several of the golden rules of javascript best practices, first and foremost being don’t modify objects you don’t own. Another issue with this is that each user-defined Renderable subclass requires a different set of extentions to the prototype and trying to use prototypical inheritance runs into the same issues as the naive solution.

Enter Dean Edwards iframe sandbox solution – the idea here is that you create a hidden <iframe> and steal the Array object from the iframe execution context.

This works because browsers sandbox the javascript execution environments so that each frame owns a unique set of native objects. Here we simply “borrow” ( read: steal ) one from a new iframe and send it back to our execution environment. As you might imagine, this technique comes with some caveats:

Keep that iframe around and attached to the DOM – it still technically owns our array.

This approach doesn’t work in non-browser environments.

Number 2 is not a problem this purpose as glitz.js is already tied to the browser in other ways. Additionally, if it were ever to be ported to a server context this same methodology could be recreated with a slightly different technique, such as borrowing Array from a VM in node.js.

Ultimately this is the technique I used for a major update to the way that glitz.js handles array subclassing under the hood in commit 5c5d183 and also ended up putting together gimme a tiny stand alone library to automate the process of “borrowing” natives.