Quantcast
Viewing all articles
Browse latest Browse all 10

Is This a Little Too Much Hacking for Prototype.js?

Prototype.js is a popular Ajax toolkit for web developers. I have enjoyed using it despite the complains I heard from people about how Prototype.js does too much JavaScript hacking that breaks other people’s code. One of the common one complains is that Prototype.js adds methods to built-in JavaScript objects (such as String object). I tend to brush such complains aside – "well, there is nothing wrong per se by adding some methods to JavaScript objects via standard permitted means".

But this recent incident with my adoption of the latest and greatest Prototype.js 1.6 really made me wonder: is this a little too much hack for Prototype.js? Or is it just bad coding practice?

I built a little application using Prototype and Scriptaculous. The application was fairly straightforward and Prototype+Scriptaculous made building such an application fairly easy too. Well, then. I used a JavaScript compression tool to compress the JavaScript files. The tool I used is Dojo ShrinkSafe. I have used ShrinkSafe a few thousand times and it never disappointed me. Well, this time, with Prototype.js, the shrinked result disappointed me, because it didn’t work. Here is what I got when trying to run the application on IE:

Image may be NSFW.
Clik here to view.

The code that produced this error is here (compressed code):

function(_133,url,_135){
_133(_135);
this.transport=Ajax.getTransport();
this.request(url);
}

The error is in line "_133(_135): Microsoft JScript runtime error: Function expected ", which means that _133 is not a function. This compressed code corresponds to the following original code:

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,
  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },
....

So this method "initialize " is a method in "Ajax.Request " class, which extends a class called "Ajax.Base" using the class creation mechanism provided by Prototype.js. The error is in line "$super(options)". It says the argument value to parameter "$super " is not a function. So this means the caller did not pass the correct argument to this method? The code that called the above code is here(compressed code):

return new Ajax.Request(_2,_3);}

Obviously this is a little hard to decode. But If I map the compressed code back to the original code:

    return new Ajax.Request(action, options);

As I looked into Prototype.js source code, I just don’t see the first argument passed into this method call being a function at all. The "action " argument in the above code is actually a URL string. In fact, there is no place in Prototype.js that passes a function argument in calling Ajax.Request method. Since the "initialize" function itself is expecting the first argument being a function, this is fairly puzzling.

So maybe the Class creation mechanism in Prototype.js itself is doing some magic here? Well, this method looks suspetious:

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

Aaaaaahhhhhaaaa! Here we go. When creating a class by extending another class, Prototype.js actually checks the function definitions in the code that defines this new class. If the first argument to a function is named "$super ", it does some magic here by automatically creating a function to pass onto this method. This explains why the first argument to "Ajax.Request " is not a function at all, but Prototype.js has taken care of this automatically during the process of defining the "Ajax.request " class. It looked at the source code for function "initialize ", saw the first argument called "$super ", and automatically created a function internally for it already.

Well, this is clearly a clever hack. It works and it is obviously permitted by the JavaScript language. Yes, in JavaScript, you can obtain the source code of a function definition and do some magic by inspecting the source code. For example, you can trigger some mission critical processing if the 3rd and 4th words of a certain function’s source code is "Easter Egg".

However, isn’t this a little too much hacking? Doing some special processing conditioned upon how some function argument is named in the source code? What if I just happen to name the first argument of a function "$super", without any intention to have any special magic happening? What if a developer somehow decided to name the first argument to method "initialize" as shown in the above to "method" instead of "$super"? What if some day JavaScript is actually running in a compiled fashion (which means that variable names are not necessarily preserved in its source code form)? No wonder Dojo ShrinkSafe didn’t work here because ShrinkSafe renames function arguments to shorter names. -Well, ShrinkSafe made a reasonable assumption – who would expect someone to write code conditioned upon how a variable is named in the source code?

Isn’t this just a little too much hacking, Prototype.js? Well, I could be wrong, but I couldn’t help but feel exactly how a nervous mother would react to a "look, Ma, no hands" kid dangling on a four floor balcony rail.


Viewing all articles
Browse latest Browse all 10

Trending Articles