Saying Goodbye To Prototype

Maybe its just me, but what I want from a JavaScript library seems to be diverging from what Prototype provides. What I want, in order of importance, is:

  • A cross-browser API that hides some of the major differences between Internet Explorer and standards compliant browsers
  • Unobtrusive
  • As small as possible, and if that’s not possible, then at least modular
  • A selector API

Where Prototype really falls down is on points two and three – its very obtrusive, getting larger by the day and isn’t modular.

Accepting Something for What It Is

Prototype’s greatest sin is its disdain for JavaScript.You can see this disdain shine through in a number of ways.

First, Prototype originated as part of Rails, which provides helpers that use Ruby code to generate JavaScript. If programs could talk, Rails would be saying “Let me take care of this for you since you certainly don’t want to dirty your hands with JavaScript.”

Second, Prototype wastes over 200 lines of code (about 5%) duplicating Ruby’s Enumerable API in JavaScript, for no obvious reason except the developers prefer Ruby’s way of doing things. The problem is that Ruby’s Enumerable API is based on one of the core features of Ruby – its elegant use of anonymous functions (called blocks) to apply snippets of code to a sequence of items. JavaScript has first-class anonymous functions, but it doesn’t have the language support for using them as iterators. As a result, Prototype’s JavaScript code doesn’t look natural because it is working outside the design strengths of JavaScript. And more importantly, it forces Prototype into using exceptions as a iteration signaling method, which is a nasty hack.

For example, let’s look at the any method. In Ruby, any? returns true if an item in a list matches some criteria. Thus to find if any number in an array is odd you would write this:

[2, 4, 6, 8, 11].any? do |value|
  value.even? # even? is from Rails, not Ruby 
end

In my view, porting any to JavaScript is of dubious value at best. But let’s look at the contortions that Prototype has to go through to do it:

any: function(iterator, context) {
  iterator = iterator ? iterator.bind(context) : Prototype.K;
  var result = false;
  this.each(function(value, index) {
    if (result = !!iterator(value, index))
    throw $break;
  });
  return result;
}

each: function(iterator, context) {
  var index = 0;
  iterator = iterator.bind(context);
  try {
    this._each(function(value) {
    iterator(value, index++);
  });
  } catch (e) {
    if (e != $break) throw e;
  }
  return this;
}

_each: function(iterator) {
  for (var i = 0, length = this.length; i < length; i++)
  iterator(this[i]);
}

The any method calls each with calls _each which then calls your method. And since JavaScript doesn’t support returning values from an anonymous function used as an iterator (there is no yield keyword like in Ruby), the any method is forced to throw an exception (see $break) to signal that an element has been found. That might seem like a small offense until you are trying debug JavaScript code using Venkman and keep interrupted by meaningless exceptions (which happens if you’ve asked Venkman to stop at all errors and exceptions).

More examples of trying to make JavaScript more like Ruby abound:

  • The addition of a Class object that introduces an initialize function, instead of just accepting JavaScript’s combined constructor/initalizer idiom
  • A number of useless additions to the String class (methods like succ, times, etc) – 100 plus lines of code
  • A number of useless additions to the Array class (methods like succ, times, etc) – a bit less than 100 lines of code

The end result is that over 10% of Prototype is wasted trying to add Ruby like-features to JavaScript that don’t fit well, simply because the Prototype designers prefer Ruby’s idioms over JavaScript’s idioms. The obvious problem is that Prototype is a JavaScript library, not a Ruby library.

Lay Off My Prototypes

Prototype also fails miserably on the unobtrusive test. In its first version, Prototype added methods to JavaScript’s Object prototype – which is a big no-no. Not learning from its past mistakes, the latest version of Prototype has this gem:

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
  attributes = attributes || { };
  tagName = tagName.toLowerCase();
  var cache = Element.cache;
  if (Prototype.Browser.IE &amp;&amp; attributes.name) {
    tagName = '<' + tagName + ' name="' + attributes.name + '">';
    delete attributes.name;
    return Element.writeAttribute(document.createElement(tagName), attributes);
  }
  if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
}).call(window);

Take a good, long look at this method. It replaces a browser’s built in Element object, which is used to represent elements in a DOM tree, with an Element function. Replacing a core browser object is nuts. Especially for the ridiculously small payoff. Instead of writing this:

var element = document.createElement('foo')
element.id = 7

This change lets you write this:

var element = new Element('foo', {id: 7})

And how many times does Prototype use this function? A measly 4 times! And to add insult to injury, the code as written is broken because it breaks the prototype chain. The last line in the function should be:

this.Element.prototype = element.prototype

Without this line, any custom extensions you’ve made to the Element object are lost. Trust me, it took a good long time to debug why our code no longer worked.

Time for a Diet

Finally, Prototype is getting bigger with every release. Version 1.5 weighs in at 3,396 lines of code while version 1.6 is 4,307 lines, a 27% increase. I’m sure the additional code is useful, but I’m also sure there are great swaths of Prototype that I don’t need. Unfortunately, Prototype doesn’t provide a mechanism to package up only the parts of it you want. When the library was smaller, that was a reasonable decision. But as Prototype continues to grow, there will come a point where its benefits are outweighed by its weight (and for me I’ve passed that point).

So What Next

The last few years have been JavaScript’s golden years, marked by an amazing outpouring of experimentation and creativity that has led to a number of great JavaScript libraries. A huge benefit of this work is revealing the pain points, beyond cross-browser compatibility issues, of working with JavaScript. These issues include the lack of a Selector API, better iterators, better chaining of DOM methods, wordy method names (getElementById), etc.

Of course each library takes its own approach to solving these problems, and with that comes a downside – lockin. For large JavaScript projects, switching between libraries is a boring, tedious, time-consuming undertaking. Which is the reason we’ve remained with Prototype for as long as we have and will continue to do so for a bit longer while we plan our migration to a new library.

  1. Dave Smith
    February 1, 2008

    I don’t currently use prototype.js, but was looking into potential libraries, as to date I’ve been working with my own ad-hoc code for DOM manipulation – so I’m glad I came across this post. For someone who’s not yet married to prototype.js, do you recommend any viable alternatives at present?

    Reply
  2. February 1, 2008

    Dave beat me to it… what are you leaning towards? I’ve done some work with prototype.js and while I’m far from a Javascript expert, I did get the sense that it was not fitting like a glove.

    Reply
  3. February 1, 2008

    I really don’t think that the LOC metric to determine size is a great metric. The LOCs could be for readability or even been added just by breaking up larger methods. I’m not saying that I disagree with what your saying about what we need in a JavaScript library.

    I think there are plenty of places that prototype can be improved

    Reply
  4. February 1, 2008

    oops hit enter in a field and it submitted.

    The great thing about it is you have all the code at your finger tips. Why not do something to make some of these improvements? Make prototype better or get some people together and write your own library.

    Reply
  5. Charlie Savage –
    February 1, 2008

    Dave and Allan,

    Right now it looks like Ext or JQuery. I’ll probably right a blog post about Ext since it really touches on something that I struggle a lot with – the document/application dichotomy in web applications.

    Reply
  6. Charlie Savage –
    February 1, 2008

    Hi Amos,

    Yes, we do have a lot of our JavaScript code and we do fix up parts of Prototype we don’t like.

    But every piece of software has its view of the world (what the Rails community likes to call opinionated software) and if your view is different than the software’s view then its fruitless to continue using that software.

    It sure seems to me that Prototype views JavaScript as nothing more than a necessary evil, and I disagree with that view.

    Reply
  7. Charlie Savage –
    February 1, 2008

    Amos,

    I agree that Lines of Code (LOC) is usually a lousy metric. However, for web apps it has obvious value because of the delay caused by browsers downloading scripts.

    Prototype uncompressed is around 125k. Speakeasy’s [speed test](http://www.speakeasy.net/speedtest/) shows that my cable connection’s download speed is roughly 280KBs, so the total download time is 0.44 seconds. Obviously that can be made better by compressing Prototype, setting up caching correctly, etc.

    If we were just using Prototype, then I’d say that’s fine. But its common to use other libraries with Prototype, such as Scriptaculous, and in our case we have our own JavaScript libraries for mapping that are fairly big. Between all this code, we end up with too much JavaScript, and the all code contained in Prototype that we don’t use is an obvious target.

    Reply
  8. February 2, 2008

    I personally ditched prototype some times ago for those exact reasons.

    I now use almost exclusively the “X library” [ http://www.cross-browser.com/ ] functions, which can be packaged independently, and is closer to the DOM approach.

    Give them a look, you might like them.

    Reply
  9. Nicola
    February 2, 2008

    I always had the same feelings about Prototype and in fact I used it only for small and quick projects or when imposed by the circumstances.

    Instead I’m a fan of dojo toolkit but I believe it isn’t the best fit for every situation and surely there are good alternatives. Anyway it works particularly well for rich clients or when there is plenty of Javascript.

    Reply
  10. February 2, 2008

    Did you have a look at mootools? http://www.mootools.net/

    Its first release 18months ago (or so) was a bit ropey, but I’ve been using the latest versions for the last couple of weeks and have found it very nice to work with.

    The modular download is especially nice – pick the features you want and get the modules as uncompressed with comments, without comments or compressed a couple of different ways.

    I haven’t poked around in the source code to see how it implements things – may use some of the nasty techniques you describe.

    Reply
  11. February 3, 2008

    Have you looked into the [Dojo Toolkit](http://dojotoolkit.org/)? There has been a *lot* of work done in making it smaller and faster over the 18 months. And we’re really careful about not fscking with the built-ins.

    One of the reasons I like Dojo is that you can start with something similar to JQuery (dojo.core) but you never out-grow it – managing your code modules and dependencies, layered builds, widgets, data/graphs/grids, internationalization, accessibility and so on.

    Feel free to get in touch with me if you have questions or want to know more.

    Reply
  12. February 4, 2008

    Hi Charlie,

    I understand you might be unhappy enough about some features of Prototype to write a post about it, and that’s completely fine by me (I happen to disagree with the whole size argument – [compressed](http://ajaxian.com/archives/packing-down-prototype), Prototype weights just over 20kb – but we don’t necessarily have to agree on everything).

    What I truly fail to understand though, is the reason which pushed you to rant about your issue with Element.prototype rather than actually step forward and do something about it (I’m not asking for much, even [filing a bug report](http://prototypejs.org/contribute) on our trac would have been enough).

    Remember, Prototype is open-source… and free. As such, it strives on the work of its users and contributors. Don’t you think it would be more considerate of you to give back to the community and help the framework improve rather than rant about your particular issue ?

    As a sidenote, I’m particularly interested by your use of Element.prototype. Are you building a Firefox-only application ? Did you somehow find a new way to extend elements accross browsers, etc. ? I’d be happy to discuss this with you. Please feel free to email me about it or discuss it in our [Core mailing list](http://groups.google.com/group/prototype-core/).

    Thanks,

    Tobie

    Reply
  13. Charlie Savage –
    February 4, 2008

    Hi Tobie,

    Thanks for you thoughts. You are absolutely right that I should submit a bug report – [done](http://dev.rubyonrails.org/ticket/11004#preview).

    As for consideration, I think that’s a fine line. I consider the time and effort I spent writing up my thoughts to be a contribution to the community. I tried to be fair and point out specific issues that I have, so I wouldn’t just dismiss the article as a rant.

    Hopefully you’ll also see from reading my blog that when I take issue with something I usually provide an alternate approach complete with working code (see the Rails RESTful controller, content negotiation, ruby-prof, geos, etc).

    In this case however, that’s more difficult because I don’t like how heavily influenced Prototype’s code is by Ruby. That of course is a matter of opinion, so I didn’t see much point in submitting a patch that rips out all of Prototype’s Rubyisms because clearly it would be dead-on-arrival (and rightly so).

    As far as discovering a cross-browser way of extending the Prototype object – sadly no. I was using it to add some additional functionality in Firefox to solve a cross-browser issue (for example, removeNode).

    Reply
  14. February 5, 2008

    Thanks for the report. This is now [fixed](http://dev.rubyonrails.org/changeset/8800) in trunk.

    Sorry if my previous comment came out as considering your whole post as a rant. This really wasn’t my intent.

    As you mentioned, Prototype is opinionated software, so it seems rather logical that some people will dislike all or parts of it. So I had absolutely no issue with the rest of your post (again, I have a different _opinion_ about filesize and `Enumerable`, but I’m happy to disagree).

    What compelled me to comment was the bug-related part only.

    Anyway, happy we cleared this issue.

    Reply
  15. Steve C
    February 12, 2008

    Ruby has “for” loops, but we choose not to use them because Enumerables can be very powerful. It invites use of strategies, chaining of selection and transformation of arrays, etc.

    I have my own criticisms of Prototype, but the idea that I’m not really doing JS unless I throw away layers of abstraction and OO tools that JS was sorely lacking, is silly.

    (aside: Here’s another vote for MooTools – a moderate improvement over Prototype)

    Reply

Leave a Reply

Your email address will not be published.

Top