Why I still prefer Prototype to jQuery

Like most programmers who’ve done Rails development, I’ve become very familiar with the Prototype library for JavaScript. Even apart from being built into Rails, Prototype was a natural choice for me. I knew JavaScript pretty well before I started working with Prototype, and was very pleased by how the library filled in some of the weak spots in the language. Additionally, having already been a Ruby programmer for a few years, Prototype’s philosophy seemed very natural; it clearly draws inspiration from Ruby.

Along the way, though, I’ve been hearing an increasingly strong chorus in favor of jQuery. I had taken a cursory look at jQuery and was impressed, but didn’t see anything compelling enough to make me switch. But increasingly the voices favoring jQuery have included people I really respect, including (over the past few months) several of my colleagues here at Relevance.

I’ve now spent serious time working on two different projects with jQuery. I’ve worked alongside some experienced jQuery users, and really increased my knowledge and level of comfort with jQuery. The work has included a reasonable variety of JavaScript tasks, from simple to complex, so I’ve had a chance to enjoy jQuery’s sweet spot and also to see how it stretches.

And here’s the capsule summary: jQuery is a very nice piece of work, and makes some common tasks easier than their Prototype equivalents. Where it’s good, it’s very good indeed. But its design is uneven, and its scope is limited. For me, at least, Prototype is still the tool of choice. I think it’s a richer, more thorough, and overall better designed library.

I don’t intend this to be an anti-jQuery screed (although I will rant a little about a couple of things that seem particularly poorly designed). Neither is it an exhaustive comparison of Prototype and jQuery. Simply put: praise has been lopsided in favor of jQuery of late, and I feel like singing the praises of my personal favorite.

Note: in this post I’ll be showing code examples using both Prototype and jQuery. To keep things straight, I’ll color-code the listings. Prototype samples will have a green background:

  // This is Prototype!
  $('form').hide();

Samples using jQuery will have a yellow background:

  // This is jQuery!
  $('#form').hide();

The occasional sample that shows the “Plain ol' JavaScript” way to do things will have a grey background:

  // This is ordinary JavaScript!
  document.getElementById('form').style.display = 'none';

Where jQuery Comes Out on Top

jQuery clearly has a core focus and philosophy that unifies its design. The idea seems to be that, since most of what you want to do with JavaScript is to find HTML elements and manipulate them, the design should be optimized in favor of that common use case. And jQuery certainly excels in that case. The jQuery documentation emphasizes the ability of many jQuery methods to be “chained” (because they return the jQuery object). I'm personally more impressed with the implicit iteration: when jQuery is used to find a set of elements, modifications are automatically made to all of those elements.

I would describe the “three pillars” of jQuery this way:

  • searching for elements using CSS selectors
  • operations implicitly applied to all found elements
  • ability to chain operations

Each of those are powerful features, but more importantly, they work beautifully together. They allow jQuery programmers to express complex manipulations of the DOM in very concise ways, without losing clarity.

This section isn't very long, so someone skimming this post might get the idea that I don't think jQuery has a lot going for it. But I really admire this aspect of jQuery. The coding style that arises from these features shapes the way you do everything in jQuery. Furthermore, the fact that three such simple ideas can have such enormous positive effects on code written using the library speaks volumes for the power of good design.

It's just a shame that the rest of jQuery is not as well designed.

In some ways, it's harder to make the case for Prototype, because that case depends on numerous small details, some of which are strengths of Prototype, and some of which are weaknesses of jQuery.

However, I have to say that, while jQuery wins in this area, it doesn't win by as much as some would have you believe. Most of the comparisons I've found on the web are rather ridiculous, because they compare jQuery against very poor examples of Prototype usage. Take this example:

  // Good jQuery
  $('blah blah').addClass('foo');

compared with

  // Prototype (explicit iteration)
  $$('blah blah').each(function(element) {
    element.addClassName('foo');
  });

There’s a much better way to do that in Prototype:

  // Prototype (implicit iteration)
  $$('blah blah').invoke('addClassName', 'foo');

The invoke method in Prototype will apply a named method to each element of a collection.

Prototype's DOM methods are also chainable, even when using invoke.

Where It’s a Wash

Critics of Prototype have tended to focus on three issues: code size, performance, and namespace pollution.

These days, Prototype and jQuery have about the same code size. Prototype is a little larger, especially since it doesn’t ship with a minimized version. But if you care about code size, that’s easily remedied. It’s not hard to run a JS minimizer. It’s also worth pointing out that the very small code size gap is not gratuitous; Prototype has more functionality. (In looking at this issue, I’ve compared core Prototype against core jQuery, and also Prototype+Scriptaculous against jQuery+jQuery.ui.)

Although Prototype’s performance has not always been good, it has improved a great deal, in many cases by learning lessons from how jQuery has done things (and in some cases, the patches were even submitted by the jQuery developers). Today, Prototype’s performance is close to that of jQuery, and the very poor performance Prototype used to have for some situations (and in some browsers) has been eliminated.

The other issue is namespace pollution. Prototype adds several top-level names to the JavaScript global namespace, and adds numerous new methods to built-in JavaScript classes. There is a potential for namespace collision, either with other JavaScript libraries or with new JavaScript features. (And it’s more than just potential; real collisions of both kinds have occurred.) jQuery, on the other hand, takes care to limit its use of existing JavaScript namespaces, and can be configured to use only one global name (jQuery).

I fully understand the dangers of Prototype’s approach, but after living with both it and jQuery for some time, I’ve come to appreciate that it also has important benefits. JavaScript is a language that is extremely dynamic and open, and also deeply flawed. Prototype’s developers have chosen to take advantage of that openness to try to fix (or at least smooth over) many of JavaScript’s biggest weaknesses. The fact that Prototype’s facilities feel well integrated into the language is a huge win for me, and it seems to be worth the cost. I don’t expect this argument to convince anyone; this is the kind of thing that boils down to taste and priorities. But for me, Prototype’s and jQuery’s approaches to namespace management have complementary costs and benefits, and I don’t think this is a good reason to choose one library over the other.

Where Prototype Wins

Now I’ll describe where I think Prototype has the advantage, touching on three different areas: a more polished API, some issues with the way jQuery uses this, and a broader, more versatile array of programming facilities.

API Polish

I’ve already mentioned how beautifully designed jQuery’s core is. The problem is that the parts of jQuery outside that core are often poorly designed, in ways that really frustrate me. I’ll describe four.

When using the each method for iteration, the arguments to the iteration function are in the wrong order. (I know, I know … if you program the “jQuery way” you won’t actually use those arguments. I’ll address that later.) Here’s an example:

  // jQuery (non-idiomatic)
  $.each(collection, function(index, item) {
    item.whatever();
  });

When iterating over a collection, you nearly always want to work with the element of the collection, and you rarely need to actually refer to the index. The arguments should be the other way around, so that you could omit the index argument in the vast majority of cases where you don’t need it. Here’s the equivalent in Prototype:

  // Prototype
  collection.each(function(item) {
    item.whatever();
  });

Prototype’s each does pass an index argument to the iteration function, but it’s the second argument, so it can be omitted if not used—which is nearly always.

But jQuery can’t even be consistent about this. Here’s how jQuery’s map works:

  // jQuery
  var result = $.map(collection, function(item) {
    item.whatever();
  });

The argument order for map is item, index—just what you want, but it’s sad that it’s inconsistent with each.

And jQuery’s map is broken in other ways as well … the traditional definition is that the result array has the same number of elements as the original, but with jQuery’s version that’s not true. (What I refer to as “the traditional definition” of map is the one used by Common Lisp, Scheme, Haskell, OCaml, Erlang, Ruby, Python, Perl, Scala, and more.) I won’t dwell on the details of how jQuery’s map is broken, but one consequence is that it’s impossible to use it to produce an array of arrays, because any arrays returned from the mapping function will be flattened into the result array.

jQuery has a unique method. It returns all the unique elements from an array; that is, it eliminates the duplicates. Very handy. But for some reason, it only works on arrays of DOM elements. It silently fails (by returning a copy of the original array) if you pass it an array of anything else. This is a known problem, but the bug was “fixed” by just updating the documentation to make the behavior clearer. How difficult would it have been to make it work correctly? (Answer: not very, because the bug report came with a patch and tests.) Prototype’s uniq method works in both cases.

Finally: jQuery also has an inArray method. You can call it like this:

  // jQuery
  $.inArray(value, array)

But what does it return? Well, “in array” is a question that has a yes or no answer, so I’m guessing a boolean. Let’s see what the docs say:

Determine the index of the first parameter in the Array …

Ah, it returns the index. But that’s OK … JavaScript supports the notion of “truthiness”, meaning that objects other than true and false can be used where a boolean is expected. Truthiness is useful, because it means boolean results can actually be useful outside of strictly boolean contexts, and returning the actual index in this case is a great example of that. So as long as inArray returns false, null, or undefined if the value is not in the array, we’re in good shape. But I left out part of that documentation above. The rest of it is:

… (-1 if not found).

Gack! How useless is that? The name of the method implies that it’s a boolean query, but the result value is not useful in a boolean context. And if you just try using it that way, making an assumption based on the name, you’ll have fun debugging it, because there won’t be an error; it’ll just always be true.

For fifty years, programming languages have been using two common names for the job that inArray does: index and position. Having a method called inArray that works like this is just sloppy library design.

Leave this Alone!

When complaining about iterator arguments to jQuery’s each, above, I mentioned that the “jQuery way” is not to use the arguments to the iterator function at all. That’s because when jQuery’s each calls your iterator function, it does so in such a way that the variable this is bound to the current item. So the idiomatic way of using each in jQuery is:

  // jQuery
  $.each(collection, function() {
    this.whatever();
  });

That’s the kind of decision that makes really simple cases very easy, but only at the expense of a lot of confusion when things are a bit more complicated. That’s because this is already an important variable in JavaScript, and the callback often needs to refer to the value of this from the scope in which it was defined. Functions in JavaScript are closures, and programmers often need to exploit the ability to refer to variables in the surrounding scope.

Here’s an example. I have an object (called chart), and in a method on that object I need to iterate over an array (stored in the property columns), generating a header for each item using buildHeader, which is another method on chart. Within methods of chart, of course, chart is referred to as this. The basic JavaScript way of doing that is:

  // Ordinary JavaScript, in a method body.
  for (var i=0; i<this.columns.length; i++) {
    this.buildHeader(this.columns[i]);
  }

But that manual iteration sure feels clumsy; it would be nice to be able to do that with each. Let’s do that with jQuery:

  // jQuery (incorrect example)
  $.each(this.columns, function(i, item) {
    this.buildHeader(item);
  });

But that doesn’t work; this within the iterator function has been rebound to the current item. The this variable from the surrounding scope has been shadowed by a different this that refers to the current iteration item. But unlike i and item, this has been shadowed implicitly, by jQuery’s implementation of each.

The preferred jQuery way to write the example is to provide an alias for this that doesn’t get shadowed by each:

  // jQuery (correct example)
  var me = this; // provide alias for this
  $.each(this.columns, function() {
    me.buildHeader(this);
  });

But notice that this in the iteration function is a different this than in the immediately preceding line, even though conventional JavaScript closure semantics would lead you to believe they were the same. When building a complicated class, jQuery’s (ab)use of this can make things very confusing.

Here’s how to do the same thing in Prototype:

  // Prototype
  $A(this.columns).each(function(item) {
    this.buildHeader(item);
  }, this);

Messing with this implicitly is a terrible design decision. It breaks the conventional semantics of JavaScript, rendering the resulting code unclear, and presenting barriers when you need to exploit the usual semantics. I understand the desire to have an implicit iteration variable; good names might be it or item. Unfortunately, JavaScript doesn’t provide a mechanism for jQuery to set those variables from outside the function; it can only manipulate this. But that’s not enough justification for the design choice jQuery makes.

If jQuery’s each were properly designed, one could write the basic iteration idiom this way:

  // jQuery (with hypothetical fixed 'each' method)
  $.each(collection, function(it) {
    it.whatever();
  });

Would that be so bad? No; in fact, it would be a huge improvement. This style is just as easy in the simple case, and far less troublesome in more complex cases.

Broader Scope

I mentioned earlier that jQuery has a core of functionality built around querying and manipulating DOM elements. It also has mature support for Ajax operations. Beyond that, though, jQuery doesn’t aim for very extensive facilities. (Of course there are numerous plugins and add-ons that do all kinds of things, as there are for Prototype.)

Prototype, even in the core library, has broader goals. It also has good facilities for DOM searching and manipulation, and for Ajax operations. To a greater degree than jQuery, though, Prototype attempts to raise the level of JavaScript programming across the board. To me, the result is a big win for Prototype.

For example: Prototype includes a nice, simple class-based inheritance facility for JavaScript. That might seem a bit heretical for a prototype-oriented language like JavaScript (and especially for a library called Prototype). But the strength of the prototype-oriented style of OO is that it gives you freedom to move beyond the class-based mechanisms. That doesn’t mean that classes are useless; actually, they’re the most appropriate way of organizing your code a lot of the time.

Prototype also adds useful new basic types (such as ObjectRange, Template, and a true Hash) and adds a wealth of supplementary methods to most of the built-in JavaScript types, greatly increasing the ease of basic programming in the language. For advanced JavaScript programming, Prototype’s new Function methods are worth the price of admission all by themselves. jQuery makes working with the DOM very easy, but is sparing in its assistance related to other JavaScript objects. Prototype, on the other hand, helps across the board.

So while jQuery might be easier to get started with, Prototype grows with you better. Most web developers get started with JavaScript by doing relatively simple DOM manipulation, and jQuery really excels there. But as you get more comfortable and begin trying to do more, you may find that jQuery doesn’t help you as much. Prototype makes JavaScript programming lots of fun even in circumstances where jQuery feels constraining.

Conclusion

I don’t think this makes a slam-dunk case for Prototype over jQuery. And certainly jQuery could improve; its design is not fundamentally limited. (For that matter, many of what I consider the missing pieces in jQuery are available as plugins.) It’s unfortunate that some of jQuery’s API flaws can’t be fixed without breaking compatibility, but in a future version even that may be an option.

At a minimum, though, Prototype can hold its own. For me and many others, it’s still the JavaScript library of choice.

(Thanks to Stuart Halloway, Chad Humphries, Larry Karnowski, Adam Keys, and Jason Rudolph for their comments—in some cases pro, in others con—on a draft of this article.)

Get In Touch