Thursday, July 27th, 2006

Prototypify: Running Prototype code with legacy code

Category: JavaScript, Library, Prototype

<>p>Aaron Newton, Product Manager at CNET sent us an email about his issues of using Prototype in production at a large company like CNET:

I’ve made a lot of use of Prototype but using it has been a bit of a controversy here at our network. Specifically, the extensions that are inherent in Prototype often create problems for (poorly written) legacy code and advertising javascript that we must include in our environment to make a buck.

Because it’s not reasonable for our technical producers to regress every ad that runs on our site, I hired a good friend (and far more talented programmer) to help me find a solution. He, one Alex Cruikshank, authored a chunk of code (2.25K) for us that I think could be of great use to those out there who avoid Prototype because of the way it alters Arrays and Objects; specifically, those trying to use your library in an environment where they do not control 100% of the javascript included.

The code, all 3K of it, is a wrapper of sorts that removes all the extensions to Array and Object from Prototype. Then, when you want to make use of Prototype, you just apply this wrapper to your code.

We have put the code up for Prototypify along with the documentation.

javascript
< view plain text >
  1. Prototypify = function() {
  2.  
  3. }
  4.  
  5. Prototypify.prototypified = false;
  6.  
  7. // store then remove functions from prototypes of native objects
  8. Prototypify.arrayFunctionHolder = new Object()
  9. for (x in Array.prototype)
  10. {
  11.   Prototypify.arrayFunctionHolder[x] = Array.prototype[x];
  12.   delete Array.prototype[x];
  13. }
  14.  
  15. Prototypify.stringFunctionHolder = new Object()
  16. for (x in String.prototype)
  17. {
  18.   Prototypify.stringFunctionHolder[x] = String.prototype[x];
  19.   delete String.prototype[x];
  20. }
  21.  
  22. Prototypify.numberFunctionHolder = new Object()
  23. for (x in Number.prototype)
  24. {
  25.   Prototypify.numberFunctionHolder[x] = Number.prototype[x];
  26.   delete Number.prototype[x];
  27. }
  28.  
  29.  
  30. Prototypify.proxy = function( f, proxyArguments )
  31. {
  32.   return function()
  33.     {
  34.       var needsPrototypes = ! Prototypify.prototypified;
  35.       if ( needsPrototypes )
  36.       {
  37.         Prototypify.prototypified = true;
  38.         for (x in Prototypify.arrayFunctionHolder)
  39.           Array.prototype[x] = Prototypify.arrayFunctionHolder[x];
  40.         for (x in Prototypify.stringFunctionHolder)
  41.           String.prototype[x] = Prototypify.stringFunctionHolder[x];
  42.         for (x in Prototypify.numberFunctionHolder)
  43.           Number.prototype[x] = Prototypify.numberFunctionHolder[x];
  44.       }
  45.  
  46.       if ( proxyArguments )
  47.       {
  48.         for ( var i=0; i < arguments.length; i++ )
  49.           if ( typeof arguments[i] == 'function' )
  50.             arguments[i] = Prototypify.proxy( arguments[i], proxyArguments );
  51.       }
  52.  
  53.       var out = f.apply( this, arguments );
  54.  
  55.       if ( needsPrototypes )
  56.       {
  57.         for ( x in Array.prototype )
  58.           delete Array.prototype[x];
  59.         for ( x in String.prototype )
  60.           delete String.prototype[x];
  61.         for ( x in Number.prototype )
  62.           delete Number.prototype[x];
  63.         Prototypify.prototypified = false;
  64.       }
  65.       return out
  66.     }
  67. }
  68.  
  69. Prototypify.instrument = function( clazz, proxyArguments )
  70. {
  71.   for ( prop in clazz.prototype )
  72.   {
  73.     if ( typeof clazz.prototype[prop] == 'function' )
  74.       clazz.prototype[ prop ] = Prototypify.proxy( clazz.prototype[ prop ], proxyArguments );
  75.   }
  76. }
  77.  
  78. Prototypify.instrumentStatic = function( clazz, proxyArguments )
  79. {
  80.   for ( prop in clazz )
  81.   {
  82.     if ( typeof clazz[prop] == 'function' )
  83.       clazz[ prop ] = Prototypify.proxy( clazz[ prop ], proxyArguments );
  84.   }
  85. }

Copyright (c) 2006 CNET Networks, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Related Content:

Posted by Dion Almaer at 2:55 pm
16 Comments

+++--
3 rating from 8 votes

16 Comments »

Comments feed TrackBack URI

Why not just removed the extensions from prototype.js? Can anyone give me an example of where this code is useful, or where problems occur with legacy code?

Comment by dcaunt — July 27, 2006

If you read some of the previous posts you will see that there has been a stink over Prototype’s extensions of the Array object. The extensions are what make Prototype so great. You cannot simply remove them.

Comment by Mario — July 27, 2006

Removing the extentions from Prototype.js isn’t really practical, since they are many and since Prototype makes use of those extensions in it’s own code. You would basically rewrite the entire library. This would create a fork in the code and upgrades would require re-authoring. Additionally, if you wanted to use a library built on Prototype (like Scriptaculous or moo.fx) you’d have to rewrite them, too. This way, you don’t have to alter these libraries at all.

The problems with legacy code or code from 3rd parties (such as ads) come when they try to use objects like Arrays in ways that they expect to behave as normal javascript. Specifically if you write a for loop as:

for (objects in myArray) {…}

Your loop will eventually run into all the extentions of the Array object (.each, for example) and your code will break.

This means that if you have 3rd party code or legacy code that isn’t written to avoid these problems (and why would they be?), you can’t have Prototype in the environment without something like this wrapper without a conflict.

All of this is outlined more clearly in the documentation (linked above in the article).

Comment by Aaron N. — July 27, 2006

@Aaron: you said “…specifically if you write a for loop as:
for (objects in myArray) {…}…”

You’re right, and hopefully people are starting to realize that this is the WRONG way to iterate the items of an array. All the above is doing is treating myArray as an Object (not an Array), so yes, it would make sense that you’d get all the properties of the Object just like any other js Object (because they are all just associative arrays, or hash tables if you will). Use numeric indexes to iterate arrays, that is the correct way people. Now, why in the world the above syntax EVER worked for the items in the Array’s indexed collection… that’s the real question. Hopefully js 2.0 will not repeat that mistake, and Array will have a property called items into which the indexed items will go.

Comment by Ryan Gahl — July 27, 2006

…very cool post, thanks for sharing this.
Mark Holton

Comment by Mark Holton — July 27, 2006

Thank you Aaron Newton and Alex Cruikshank. This is truly awesome!!!

Comment by Mario — July 28, 2006

That’s where Flash’s Actionscript implementation is interesting: it is possible to make the prototype extensions non-enumerable in a for..in statement.

Comment by Philippe — July 28, 2006

re: “this is the WRONG way to iterate the items of an array”

I know this, and you know this, but some dude at an ad agency doesn’t know this. It’s not practical for our staff to QA every single ad that runs on our site; there are just way too many of them every day. So the point here is that a) people out there DO iterate through arrays in this fashion, b) not all of us know this general rule of arrays, c) legacy code might make use of this type of iteration because, well, it worked, and d) Prototype alters the fundamental structures of object classes in a way that not everyone out there expects to encounter.

By allowing people to turn Prototype “off” and “on” this script lets your code go crazy without breaking older stuff or stuff written by others. From a philosophical perspective, this might not be ideal, but from a business perspective (such as CNET’s) it’s very practical

Comment by Aaron N. — July 28, 2006

If the Prototypified code throws an exception – all bets about the non-Prototype code working are off. A fix might be as simple as adding a try/catch block around the wrapped function, but I’m not sure.

Comment by Prototypify won't work with exceptions — July 28, 2006

So you are saying if you have a javascript error it may cause other scripts to not work right? Isn’t that a no duh comment???

I do strangely enough see your point though.

Comment by Mario — July 28, 2006

Mario, Mario, Mario… do you find it strange that your code should handle all exceptional cases? When an exception is thrown from a Prototypified wrapped function call it will pollute your non-Prototype’d code’s Array and Object prototypes. So you are not protected from Prototype’s side-effects. This defeats the purpose of using Prototypify in the first place.

Comment by Prototypify won't work with exceptions — July 29, 2006

I said I see your point. I think I would just remove the js error…

Comment by Mario — July 30, 2006

This wrapper now allows you to use the wonderfull (although no longer supported) fValidate library by Peter Baily with prototype/scriptaculous. Before if you called the makeSortable function in Scriptaculous and had the fValidate library installed it caused an error in IE… This fixed it. Thanks for the post

Comment by Chris — July 31, 2006

> If the Prototypified code throws an exception – all bets about the non-Prototype code working are off.
This is sort of true. The worst case scenario is that, if a prototypified function throws an exception, any non-prototypified code run after the exception and before the next successful prototypified execution block will see the prototyped functions as it iterates through an array. I looked into handling the error, but the compatibility issues with the try/catch/finally block seemed to outweigh the original problem. I didn’t spend much time on it, so I could easily be wrong. If it could be done reliably, un-prototypifying in a finally clause would be a definite improvement.

Comment by Alex Cruikshank — July 31, 2006

This is great – Prototype extensions to Array really messed up my code when looping through associative (not numeric indexes) arrays. Associative Arrays are great tools, which Prototype seems to break. Or perhaps there is a method in Prototype to iterate over associative arrays (essentially objects in javascript), without touching the prototype extensions…?

Thanks for sharing this!

Comment by Hakon — August 15, 2006

Prototype does in fact have a method to iterate through associative arrays. The myArray.each function will iterate through the values, but if you want to get both the value and the key, you will use a Hash (http://www.sergiopereira.com/articles/prototype.js.html#Reference.Hash).

Personally, I don’t like to use arrays this way and instead maintain an array of names and another object of name->value pairs. This creates an index of all the keys for quick searching and the like. Prototype offers up the myArray.indexOf(value) but if your object is complex, this doesn’t always work.

Comment by Aaron N. — August 15, 2006

Leave a comment

You must be logged in to post a comment.