Prototype vs. YUI Round 2: I love $
Probably the most commonly needed Function for a Web Application Developer (WAD), is document.getElementById. As you will most certainly need to retrieve HTMLElements from the DOM by the element’s ID. Using ‘getElementById’ is perfectly valid, but has potential drawbacks such as:
- ‘document.getElementById’ is verbose
- multiple elements cannot be retrieved with one simple call
- Some browsers, especially old browsers, do not support this call.
- The more IDs you use in your document, the longer the seek time of this method.
As the title says, I am in love with the ‘$’ (I am also in love with money, in case you were wondering). The ‘$’ function was first developed by Prototype (and IMHO is Prototype’s greatest contribution to the JavaScript community). My love for the ‘$’ function is two-fold:
- It is elegant, simple, and concise
- Using the $ symbol as a shortcut, should be safe and most people did not know this was possible (so it is cool)
But beyond those points, let us really consider what an element retrieval function should and should not do:
Dos
- Process lists of elements as well (both arguments list and arrays)
- Handle all x-browser issues and old browsers, or degrade nicely
- Caching. It is faster to maintain an array for all previously retrieved IDs, than to retrieve them each time
- Do nothing when passing an HTMLElement (it should be acceptable to use ‘foo= $(foo)’, just to ensure that is an HTMLElement
Do Nots
- Attach a bunch of variables and/or functions to the HTMLElement*
- DO NOT do item 1
* This is my single biggest gripe with pretty much all JavaScript Frameworks. Why do each of my HTMLElements need a slew of helper functions that are already available to me elsewhere? I am not that lazy and frankly it is expensive to add Function references to every HTMLElement that I retrieve. Plus: you increase your chances of circular references causing memory leaks, different Frameworks and Toolkits may conflict with each other, and some future version of JavaScript may define that same function.
That said, let us move on to comparing YUI ‘YAHOO.util.Dom.get’ vs. Prototype ‘$’. I have created an simple rating system that I will be using for this and future discussion. I will walk through each Function and rate them using a point system, where you get plus points for each ‘Dos’ (ex. +1) and minus points for each (ex. -3) ‘Do not’ used. Here is the newest prototype ‘$’ function from version 1.5.1:
Prototype ‘$’ Function
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
{elements.push($(arguments[i]));}
return elements;
}
if (typeof element == 'string')
{element = document.getElementById(element);}
return Element.extend(element);
}
I am happy in this release that they allow you to pass a list of arguments and retrieve multiple HTMLElements at the same time (+1). I am going to give the Function (+1) for using the ‘$’ name. However, it does not allow you to pass in an array as an argument, which is important because sometimes you will have gotten your lists of IDs from a method that created an array of Strings (+0) and will be required to use a work around to pass these Strings into this Function. The Function does retrieve arguments that are Strings and returns the non-string Objects (hopefully HTMLElements) (+1). Unfortunately, the method attaches needless and dangerous methods to the HTMLElement that you pass or retrieve (-3). It does not work with old browsers or degrade well (+0) and has no built in caching system (+0). This gives Prototype an overall rating of (0), which is pretty poor.
YUI ‘YAHOO.util.Dom.get’ Function
get = function(el) {
if (!el) { return null; } // nothing to work with
//
if (typeof el != 'string' && !(el instanceof Array) ) { // assuming HTMLElement or HTMLCollection, so pass back as is
return el;
}
//
if (typeof el == 'string') { // ID
return document.getElementById(el);
}
else { // array of ID's and/or elements
var collection = [];
for (var i = 0, len = el.length; i < len; ++i) {
collection[collection.length] = Y.Dom.get(el[i]);
}
//
return collection;
}
//
return null; // safety, should never happen
}
As with Prototype you are allowed to pass multiple arguments at the same time (+1) and although you cannot pass in an array of arguments, you can pass an argument that is an array, which is more powerful (+1). There is no ‘$’ function short-cut (+0), but I always map this method to the ‘$’ symbol anyway, so it is really not a big deal. The function does retrieve arguments that are Strings and returns the non-string Objects (hopefully HTMLElements) (+1). It does not attach needless/dangerous methods to the HTMLElement (-0). Unfortunately, it does not work with old browsers (+0) (it does degrade well, by return ‘null for unexpected values) and has no built in caching system (+0). This gives YUI ‘get’ Function an overall rating of (+3), which is average, however, if you map the ‘$’ symbol to the ‘get’ Function, then it would bump the rating up one, making YUI ‘get’ Function slightly above average.
Therefore, using my metrics, YUI’s ‘get’ Function is better than Prototype’s ‘$’ Function. I recommend using the YUI one and mapping it to a function named ‘$’ in the global space, emulating Prototype. Not only will you be able to use the concise ‘$’ symbol to retrieve HTMLElements, but doing this will allow you to use YUI with some of Prototype-based Toolkits and Frameworks, without the down-side of using Prototype. Obviously, there is a little room for improvement, as there is no caching yet and there is not a work around for browsers that do not support the ‘document.getElementById’ method. However, 95%+ of internet users (with JavaScript enabled) will have a browser that supports ‘document.getElementById’ (and this number increases every year), and caching gives a marginal improvement only when repeatedly retrieving the same element using the ‘get’ Function (and may be detrimental to performance if you use your own caching system).
Lastly, I want to leave you with a slightly modified YUI ‘get’ function that I use. I use YUI’s namespace approach and attach everything to an Object named ‘Core’, which I later shortcut key methods (such as ‘get’) as part of my JavaScript build process. Here is my method (it will be made available as part of my Core.Util.Dom package after I have further discussed DOM manipulation:
Core.Util.Dom.get: function() {
var multiArgs = (1 < arguments.length); // arguments is not an Array
var elem = multiArgs? arguments: arguments[0];
//
// nothing to work with
if (! elem) {
return null;
}
//
// assume is a DOMElement, but could be any Object
if (! isString(elem) && ! isArray(elem) && ! multiArgs) {
return elem;
}
//
// ID
if (isString(elem)) {
return document.getElementById(elem);
}
// array of ID's and/or elements
else {
var collection = [];
for (var i = 0, j=elem.length; i
}
return collection;
}
}

Prototype’s approach definitely inspires a love-it-or-hate-it reaction.
With regards to extension to elements accessed with $ or $$, the list of mix-ins is large, but incredibly useful:
http://www.prototypejs.org/api/element
With regards to globals and object extension, I am almost amused at their confidence at times. Indeed, even their self-penned description of DOM elements is a little dramatic:
“Before you pursue, you really should read “How Prototype extends the DOM†which will walk you through the arcane inner workings of Prototype’s magic DOM extension mechanism.”
Arcane and Magic?
Really?
In the end, though, no one is putting a gun to our head - and we can very easily hack the source.
We can’t ever forget that most of that library is directly inspired by “The Ruby Way” - they do what works for them, and it seems to work for a lot of other people as well. I encourage anyone who thinks Prototype’s approach is insane to take a look at “Programming Ruby”:
http://www.amazon.com/Programming-Ruby-Pragmatic-Programmers-Second/dp/0974514055/ref=pd_bbs_sr_1/104-6382103-6883909?ie=UTF8&s=books&qid=1181320753&sr=8-1
Comment by Geoff Moller — June 8, 2007 @ 9:52 am
Thanks for the comment, Geoff. It is true that Prototype is inspired by Ruby and mirrors the language fairly well. My thoughts are: the Ruby language works great for Ruby, but it does not translate well to JavaScript.
Comment by admin — June 8, 2007 @ 9:59 am
Matt, thanks for “Prototype vs. YUI†series of posts.
I personally used both frameworks and really enjoyed readying someone else inputs.
What do you think about comparing execution scopes (bind vs additional function parameters) in Round 3?
Comment by Andrew Bidochko — June 28, 2007 @ 11:35 am
Great comment Andrew. I agree that the execution scopes methods are important and will put some thought into it for my next head-to-head comparison.
Comment by admin — June 28, 2007 @ 1:04 pm
I agree wholeheartedly with your analysis. Personally, I prefer a library to follow the existing rules and conventions of the language, rather than try to turn Javascript into Ruby or Java or anything else. That being said, $() is handy, so I’ll often set “var $ = YAHOO.util.Dom.get;” inside the local scope of my code.
If you find that the syntactic sugar is tasty, then it’s worth checking out the YUI Element utility.
http://developer.yahoo.com/yui/element/
Rather than attach these helpers to every element, YUI gives you the tool to attach them yourself where and when you as the developer feel it’s most appropriate. It’s not intended to be applied willy-nilly to any and every Dom element.
Comment by Isaac Z. Schlueter — August 20, 2007 @ 6:54 pm
Isaac, great comment.
I too find the ‘$’ Function to be useful. I also tend to import the ‘$F’ Function and a few others as well. My build process includes a file ’shortcuts.js’ right after the Framework files where I define about 20 shortcuts.
If you find yourself using a Framework with long namespaces like ‘YAHOO’ then defining ‘$’ to the ‘get’ method as Isaac mentioned can also reduce file sizes by a measurable amount (2-10%).
On a less positive note, be careful about using syntactic sugar, as it is very easy to catch yourself in a circular reference. For this reason, I avoid using it.
Comment by admin — August 23, 2007 @ 5:59 pm
I’m not sure what you mean by “catch yourself in a circular reference.” I do firmly believe that any syntactic shortcuts, along with other global variables, should be kept within a local scope. That prevents collisions. Circular references are only a problem if they cross between Javascript space and DOM/XPCOM space. http://foohack.com/2007/06/msie-memory-leaks/
Circular references *within* Javascript space are natural and common. Consider, for example, the reference chain that connects an object to its constructor and prototype:
var X = function () { };X.prototype = { constructor : X };
var y = new X();
The X function, via its activation object, has a reference to “y” variable. Via the constructor property on the prototype (which is always there, whether you specify it or not,) “y” has a reference back to X. Or even more obvious, and just as harmless:
var a={};a.b=a;
The variable can now be referenced as a.b.a.b.a.b.a.b.a.b… Though it would of course be silly to do this, it won’t cause any problems beyond the extra lookup time. The minute “a” falls out of the available scope, the mark-and-sweep algorithm will clean it up.
Circular references that cross into DOM/XPCOM space *should* be just as harmless, and in most browsers they are. Unfortunately, IE 6 is the browser that is used most, and it is the one with the memory-leaking bug.
Comment by Isaac Z. Schlueter — August 24, 2007 @ 10:00 am