Speeding Up Prototype's $ Function
December 11th, 2006
Often times in prototype and in general with JavaScript, when you create a new class that does something to an element, you cache that element in the initialization method of the class. When I say the the element is cached I mean that the $(element) is not called each time you reference this.element throughout your class (which may be several times), only the first time. This speeds things up considerably, as getting an element by it’s id is mildly expensive as far as JavaScript functions are concerned. Take for example, prototype’s Abstract Event observer class:
Abstract.EventObserver.prototype = {
initialize: function(element, callback) {
this.element = $(element);
this.callback = callback;
// some more initialization stuff
}
// the rest of the class
}
Because this.element is cached, each time you need to know which element has the event observer on it you can reference it with this.element which is the same as if you called $(element) each time. Ok, so what am I getting at?
Well, I’m working on showing a hierarchy of pages in a current project. The first time you click on a page with children, the children get loaded below the parent and so on and so forth all the way through the depths of the tree. After the children pages have been loaded for a parent, clicking the parent page simply shows or hides the children. What I noticed is that there are several places where I’m getting elements by their id (parent, children, links, etc.) so I started to cache those $() calls in arrays. I then created a finder method for each type of element that would check if the element had been found and if so would just return the value of the array rather than actually making the $() method call.
I immediately noticed a speed difference when showing and hiding elements. No longer was I making $() calls to get the element so the only thing the link had to do on click was show or hide. The speed was noticeable enough that it led me to further thought.
The Point
If it’s good to cache the reference to $() in a class initialization method, why not just do it for each element that you call $() on? Thus was born Speedy Gonzales.var SpeedyGonzalez = {};
SpeedyGonzalez.Version = 0.1;
Object.extend(SpeedyGonzalez, {
elements: $A([]),
findElement: function(element) {
if (!SpeedyGonzalez.elements.include(element)) {
SpeedyGonzalez.elements[element] = document.getElementById(element);
}
return SpeedyGonzalez.elements[element];
}
});
SpeedyGonzalez.$old = $;
function $() {
var results = [], element;
for (var i = 0; i < arguments.length; i++) {
element = arguments[i];
if (typeof element == 'string') {
element = SpeedyGonzalez.findElement(element); // only change to prototype's $()
}
results.push(Element.extend(element));
}
return results.reduce();
}
The basics of Speedy are store the old $ function in SpeedyGonzales.$old and then rewrite it to call SpeedyGonzales.findElement(element) as opposed to document.getElementById. The findElement method checks if the element is currently in the elements array, adding it to it if it is not found and then returning either the newly stored element or the cached reference. Nothing complex at all. The best thing is that there is no work for you. Just like lowpro (a fine piece of work by the way), all you have to do is include the script in the head of your document after prototype is included and you are good to go.
The only downside I can see to this is that storing all these elements in an array will use memory but I did a few checks of caching one thousand elements and their was no noticeable slowdown. Does anyone else know of a downside to this?
I’m using it on my current work project and loving it (along with the other developers). You can grab the latest copy from my svn repository. Feel free to post suggestions, problems and improvements in the comments.
4 Responses to “Speeding Up Prototype's $ Function”
Sorry, comments are closed for this article to ease the burden of pruning spam. If you have any further comments, just send me an email.

December 12th, 2006 at 10:57 AM
The only possible drawback I can think of might be where you are dynamically creating and destroying elements on the fly. I’m speaking off the cuff; I’ve not tested it as yet. Otherwise, this is a really neat idea. Thanks!
December 12th, 2006 at 01:44 PM
I thought about that too. For my purposes, I’m not destroying and recreating the same element and I haven’t tested it yet. If I get a chance I will or if anyone else does, let me know and I’ll fix it.
February 19th, 2007 at 02:30 AM
When I add this script into my application.js file I get results is not a function. For some reason this is not working for me, especially since it’s a simple copy and paste. I am using rails edge, if that helps.
February 20th, 2007 at 11:43 PM
@Ben – Just including the javascript gives you the error or how are you trying to use it?