Why I Don't Use The prototype.js JavaScript Library

When it comes to JavaScript there is one issue for which there seems to be two polarised camps, and that is the question of extending the inbuilt JavaScript Array and Object types via the prototype object. There are those who do, and those who don’t.

I am most definitely one of those in the “Don’t, because it ‘would be bad’” camp.

Now, thanks to the Web2.0/Ruby On Rails/Nuevo Bubble phenomena there is a widely used library that makes great use of the prototype object and that is Sam Stephenson’s prototype.js library.

I ran into an issue 6 months ago and decided I would never ever use prototype.js, despite the fact, and I don’t say this often, that after an examination of the code, prototype.js is an inspired work of art.

What I and many many others have discovered is that using the prototype object on the Array and Object inbuilt types increases the chances that your code will conflict with existing or external code. It makes your code not play well with others, so once you start using prototype.js, you have to keep using prototype’s paradigm because by extending Array and Object via the prototype object it secretly modifies some of JavaScripts default behavior.

It’s the crack cocaine of JavaScript.

This can be a good thing. If you don’t want to waste time writing your own JavaScript libraries and learning how everything really works, then using prototype.js and the libraries that extend it (e.g. Open Rico) is a very good way of developing. You will save time and money and all you need to learn is “the way of prototype.js”.

Now the entire tasty raisin for the MetaWrap JavaScript libraries is to allow others to easily remix MetaWrap applications via a client side API that can be invoked via XML. The result is that CSS, HTML and JavaScript can be injected into the application, or XML and HTML at any point in the rendering pipeline of the application.

So I simply had to reject prototype.js because, out of the box, the very first time I tried to use it – it snuck out and cut the throat of the JavaScript I was using that relied on performing a for(x in object) on the contents of an Array.

In JavaScript, value types are subdivided into primitives and objects. Objects are entities that have an identity (they are only equal to themselves) and that map primitive properties to other value types, (“slots” in prototype-based programming terminology) – see these testcase #5 – #7. Because of this behavior JavaScript objects are often mistakenly described as associative arrays or hash tables, while functionally they behave like an associative array/hash table, technically this is not their true nature.

Despite this the JavaScript programming world has come to rely on these objects behaving as predictable associative array/hash tables – and prototype.js breaks this.

There is no object more galactically useful than a good associative array/hashtable. There is no problem that can’t be solved with a large enough hash table. In highly granular interpreted languages like JavaScript it provides a way to dip into pure native brute force computing power mostly unhindered by the language interpreter.

It is because this has been tampered with that I have to turn my back on prototype.js and say Nyet!

Now lets look the issue in some detail and in particular at the way that JavaScript uses arrays and the way that we create and access data in those arrays.

Although in reality there is one method to access data in an array array[value] = object;, there are two defacto methods. The first is numerical indexing, whereby you access each element in the array by a single number – this is supported by the inline constructor, shorthand and the array.push functions that add items to an array automatically via a numerical index.

The following three approaches will create an identical numerically indexable array.

E.g.,

“Specified numerical index”

var array = new Array();
array[0] = "Apple";
array[1] = "Orange";
array[2] = "Banana";

“Push”

var array = new Array();
array.push("Apple");
array.push("Orange");
array.push("Banana");

“Inline Constructor”

var array =new Array("Apple","Orange","Banana");

“Inline Constructor Shorthand”

var array = ["Apple","Orange","Banana"];

The elements of this numerically indexable array can be accessed via the following methods…

By directly indexing into the array with a number using for(;;)

var i;
for(i = 0;i >array.length;i++)
{
  alert(array[i];)
}

or via the for(in) iterator

for(var i in array)
{
   alert(array[i]);
}

But I mentioned there were two methods to store and access data in arrays..

The second is as an ‘associative array/hash table‘, where in the index is not an number, but one of the other primitive value types (String or Float for example).

var array = new Array();
array["A"] = "Apple";
array["O"] = "Orange";
array["B"] = "Banana";

In this case array.length will be 0. There are three items in the array – but is length property will return 0.

This array is only useful if you know what the primitive value index is for a particular object. Array look-up performed natively within the interpreter so is very fast, which makes for a perfect hash table structure, but if you want to iterate all items in the array, you have to use the for(in) iterator method.

Sadly – protoype.js breaks this by adding ‘extend’ object that appears in every array.

So you have to be very careful about how you introduce external JavaScript that is not based on prototype.js. Prototype uses the JavaScript prototype level expando trick (via the prototype. keyword) to add extra objects to the inbuilt function and Array, Object and Function types.

After you include prototype, every array gets an extra element.

See these testcases.

When you execute testcase #1 this it will result in “extend A B C ” instead of “A B C” – so it fails.

Now this can be remedied by the next testcase #2 that does not use for(in) but instead uses a numeric index into the array via an old style incremented for(i = 0;i<array.length;i++).

For associative arrays, there is no hope. Prototype makes associative arrays unreliable when it comes to using the for(x in array) loop.

The only way I can protect my code is to not use for(in) for an array that is not associative, and for associative arrays, treat ‘extend’ as a sentinel that gets skipped.

// Guard against prototype
if (l_node == "extend") continue;

The problem I am describing here is the result of one of the extensions made by prototype.js (version 1.3.1)

Object.prototype.extend

There are 5 others..

Array.prototype.push

On browsers that don’t already implement push (IE5) then this will appear in an enumerated version of the array via for(x in object).

Function.prototype.bind

Function.prototype.bindAsEventListener

Number.prototype.toColorPart

Function.prototype.apply

String.prototype.extend

Advertisements

About metawrap

CTO Massive Interactive. Ex Computer Whiz Kid - Now Grumpy Old Guru.
This entry was posted in JavaScript, Rants, Web2.0. Bookmark the permalink.

27 Responses to Why I Don't Use The prototype.js JavaScript Library

  1. John Bell says:

    I’m a but confused – you mention that Array is affected, but the modification you quote is

    Object.prototype.extend

    Should thar be

    Array.prototype.extend ?

  2. Good question.

    No – it is correct as it reads. Array inherits from Object, so anything you add to Object, is visibile in Array.

    I have modified one of the testcases to make this clearer.

    http://test.metawrap.com/javascript/tests/fundamental/test_49_prototype_array_foreach_collision.html#5

  3. Will says:

    This is definately serious. I am working on a Google Map CMS engine and was using Prototype for AJAX stuff, and there does not seem to be any conflicts at this point, but all it will take is one line in the API code with a for(x in o) and it will be all worthless. Thank you for highlighting this – I am writing prototype out of my app!

    BTW – wish you had pointed to some other AJAX/JS libraries that are safe.

  4. Alex Russell says:

    Hi Will,

    I work on Dojo, a library that very explicitly doesn’t do this kind of thing. MochiKit also avoids this sort of side effect, so you have lots of options.

    Regards

  5. Shane Celis says:

    There does seem to be a solution for this. I don’t think it’s a great one. I think something needs to be changed at the language level for it to be better, but the fact that it allows one the advantage of extending built-in types makes it worthwhile in my mind.

    for (var i in obj)
    if (obj.propertyIsEnumerable(i))
    foo(obj[i]);

  6. I’ve run into only one conflict between Prototype and existing code; it involved an "associative array" like the one you describe.

    The problem is that an Array isn’t meant to hold string keys, only number keys. To replicate an associative array (or hash or dictionary) you’d simply make a generic object:

    var array = new Object();
    array["A"] = "Apple";
    array["O"] = "Orange";
    array["B"] = "Banana";

    In other words, the problem you describe is caused by a specific misuse of JavaScript, and can be fixed by changing one word in your existing code.

    In the larger sense, there is a design flaw in JavaScript that shouldn’t allow this sort of thing to happen in the first place: a "for…in" loop will iterate over any properties added to the object’s constructor’s prototype *in addition* to properties added to the object itself. This is silly. It’s why extending Object.prototype is a bad idea. (http://erik.eae.net/archives/2005/06/06/22.13.54/) Once upon a time, Prototype mucked with Object.prototype, but this was corrected in version 1.4 almost a year ago.

  7. It’s great to see JavaScript/AJAX hackers now hitting an infamous issue that Actionscripters tackled several years ago. The hard lesson we learnt with Actionscript way back then was to never rely on for..in loops. (at least until the hack to hide the prototype method from iteration became well known)

    The problem isn’t that serious though. I enountered it when integrating prototype into a site that was run from the Magnolia CMS. Amongst mountains of Javascript to power its admin features, Magnolia had 2 (for…in) loops. I fixed those to use (for…array.length) and then prototype was good to go.

    Another solution is to check when iterating whether the item you’re accessing is a function.

  8. Yes, I’ve noticed that the Actionscripters have their act together. I like the adoption of the java namespacing rules.

    Check out

    https://blog.metawrap.com/blog/June6thIsJavaScriptArrayAndObjectprototypeAwarenessDay.aspx

    I’m attempting to educate and get some momentum going.

  9. tomash says:

    hello, how can i find captcha tool for my blog?

  10. Zak Kuuhn says:

    Read Andrew Dupont’s post above. It would behoove you to educate yourself before you attempt to educate others.

  11. Prototype has been re-written since this article was posted and yes now there are claims that (v 1.5) it behaves well.

    Sadly, the current official release version on http://prototype.conio.net/ is still 1.4 which has the problems addressed in this posting.

    This whole process has been a learning experience for many JavaScript developers – me included.

    Please also read.

    https://blog.metawrap.com/blog/June6thIsJavaScriptArrayAndObjectprototypeAwarenessDay.aspx

    The only way we can fix is by education, and I can assure you I have been getting plenty of that 🙂

  12. Stuart Gathman says:

    IMHO, javascript is broken. for(i in o) should not return prototype members. That is just braindead. I suspect the thinking was to make finding all methods and properties of a "class" simple. But not at the expenses of breaking simple associative array behaviour (or making extensions incompatible). Instead, a function to iterate recursively over prototypes should be used (built-in or library).

    Effectively, javascripts objects can be used as both code (prototypes) and data. There should at least be different functions/syntax for both kinds of access.

  13. You’re doing it wrong. The ‘right’ way to do it in prototype is this:

    var f = { a:1, b:2, c:3 };

    $H(f).each( function(iter) {
    console.log( iter.key+’ => ‘+iter.value );
    } );

    the $H converts it to a ‘hash’, which you can then use the same .each syntax as you can for arrays – which IMHO is much nicer than using key, array[key] anyway.

    NB: console.log comes from firebug, if you haven’t got it you need to goto http://www.getfirebug.com NOW.

  14. By the way, I agree with your point that prototype breaking your legacy code is a bad thing, and as such I also wouldn’t use it if I had to mix half a site with some old code that I couldn’t or dared not update, however ditching prototype and all it’s other amazingly useful parts because it breaks some old syntax (while providing lots of newer, nicer, safer alternative syntaxes) is not IMHO a very good idea.

    Learn them both, use whatever best suits your problem at hand. I think you’ll find prototype wins more often than not…

  15. Orion< Thanks for your comments.

    What is the overhead for $H()?

    Yes I have firebug 🙂

    Breaking external code was my main reason for not using prototype.

    Seems that 50% of coders don’t use a library at all http://domscripting.com/blog/display/92 🙂

    I’m just not enthusiastic about starting from scratch and writing everything myself or only using libraries that use prototype.

  16. Brent Starks says:

    The use of an array as a hash is improper as pointed out by Andrew Dupont above. The JavaScript Array is not a type of hash and was never meant to be anything more than an indexed array. JavaScript does not have a hash type data structure (but the JavaScript coder can easily create one).

    The array is a specialized data structure derived from the JavaScript Object (as mentioned above). It is used as a hash because those who write script and script tutorials discovered that they could use it this way (as virtually any object can be used this way – try it) and thought themselves clever for doing so. As Andrew pointed out, allowing this could be viewed as a fault in the underlying language. The mistake is in believing that it is proper simply because it can be done, or that wide-spread use of a bad coding practices makes the language "braindead" for not accommodating poor coding as proposed by Stuart Gathman above.

    By not understanding the language, those writing JavaScript are seeing legacy code break when using Prototype. While Prototype’s earlier versions were not as elegant due to the way they extended objects, the code was correct. Legacy code, in this case, is breaking because of coder error rather than Prototype errors.

    The ancestral object is being used when using an array as a hash and this is why the length property wont work as expected. Use a derivative object. The For-In loop does not work on the array because it is designed specifically for traversing objects while the standard For loop is designed for traversing indexed arrays.

    Fixing coder error makes more sense than cutting yourself off from programming tools and possible future breakage due to future versions of the language. This is especially true when you have a weaker understanding of the language than those writing the libraries as you can create more robust code and complete complex projects far more rapidly than your native abilities would accommodate.

    While I haven’t implemented Prototype yet (still studying the code) in any of my projects, the 1.5 version can be downloaded from http://www.scriptaculous.com.

  17. Brent Starks says:

    One more comment on using an Array object for a hash…. You gain the baggage rolled into the Array object while not being able to use any of the Array object’s funtionality. An instance of the Object object offers the same functionality without the baggage and the instance can be extended to have all the features expected from a good hash data structure.

  18. Andrew Martinez says:

    As it has been pointed out by others above me, that misuse of the object type is the problem and not the implementation of JavaScript.

    I am saddened to this post is causing people to exclaim that "oh, my I will not use THAT library". I wish this post was written more objectivly rather than from an omniscient I-Know-What-Is-Going-On-So-Listen-To-Me point of view.

    The only sadder point is that the real relevant comments will probably not be reached by most readers of this blog.

    =( <— I’m doing this as hard as I can.

  19. Joe Bauser says:

    Just a quick note:

    Using an Array as a hash is by definition wrong.
    For a "hash" use type Object.

    "for in" constructs used on prototyped objects will by definition return the prototyped members. This is correct behavhior as defined in the spec.

    The spec also has a solution!
    The "hasOwnProperty()" function as defined in the Standard ECMA-262 definition section 15.2.4.5 (only mentioning the extra info as proof that the function is standard and will likely not go away or change) can be used to prevent the problem you’re experiencing, and likely exist exactly for these types of situations.

    Give it a try:

    var a = new Array();
    a["joe"]=1;
    a["jim"]=1;
    a["sara"]=1;

    for( i in a )
    if( a.hasOwnProperty(i) )
    alert(i);

    Note that the result is exactly the same with or without Prototype.

  20. @Andrew Martinez: Sorry if you took my attitude to be that way. It is serious issue and if you won’t take my word for it, look at what Dean Edwards has said about it.

  21. Just to clarify – my original post was about an old version of prototype that added slots to Object.prototype
    The latest version of prototype does not do this. It still does great evil to Array, but people are prepared to accept this as long as a ‘standard’ is adhered to. That standard being "Do nothing to Object, anything else, expect it to be mutated"

    See https://blog.metawrap.com/blog/June6thIsJavaScriptArrayAndObjectprototypeAwarenessDay.aspx

  22. I use and endorse Dean Edward’s Base and Base2 Libraries… If that clears anything up 🙂

  23. Steve says:

    There is a related bug inside prototype to do with the hashing of querystring values in the Ajax object.

    If I send a Get request with a random number querystring parameter (to force no cahceing) and no other parameters, what actually comes through to my server is a the Object function toColorPart! e.g. /mysserver/test?0.345643565546

    This is traced back to Hash.toQueryString and to this.prototype._each.call(obj, function(pair) which surprise surprise, does a "for (var key in this)"

    This does seem kind of related to the discussion.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s